package eu.dnetlib.openaire.directindex.api;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.google.gson.Gson;

import eu.dnetlib.common.rmi.DNetRestDocumentation;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.functionality.index.solr.feed.StreamingInputDocumentFactory;
import eu.dnetlib.miscutils.functional.UnaryFunction;
import eu.dnetlib.openaire.directindex.objects.ResultEntry;
import eu.dnetlib.openaire.directindex.objects.ResultEntryToOaf;
import eu.dnetlib.openaire.directindex.utils.OafToIndexRecordFactory;

/**
 * Created by michele on 11/11/15.
 */
@Controller
@DNetRestDocumentation
public class OpenaireResultSubmitter {

	private static final Log log = LogFactory.getLog(OpenaireResultSubmitter.class);

	@Value(value = "${openaire.api.community}")
	private String community_api;

	@Value(value = "${openaire.api.dsm}")
	private String dsm_api;

	@Value(value = "oaf.schema.location")
	private String oafSchemaLocation;

	@Resource
	private UniqueServiceLocator serviceLocator;

	@Resource
	private OafToIndexRecordFactory oafToIndexRecordFactory;

	@Resource
	private RecentResultsQueue recentResultsQueue;

	@Resource(name = "openaireplusApisVelocityEngine")
	private VelocityEngine velocityEngine;

	@Resource(name = "indexClientManager")
	private IndexClientManager clientManager;

	@Resource(name = "resultSubmitterService")
	private ResultSubmitterService submitterService;

	@Autowired
	private IndexDSRetriever indexDSRetriever;

	private OpenAIRESubmitterUtils utils;

	@PostConstruct
	public void init() {
		utils = new OpenAIRESubmitterUtils(community_api, dsm_api);
	}

	@RequestMapping(value = {
		"/api/admin/autocommit/active"
	}, method = RequestMethod.GET)
	public @ResponseBody Boolean getAutocommit() throws DirecIndexApiException {
		return submitterService.isAutocommitactive();
	}

	@RequestMapping(value = {
		"/api/admin/autocommit/active"
	}, method = RequestMethod.POST)
	public @ResponseBody Boolean setAutocommit(@RequestParam(value = "active", required = true) final Boolean active) throws DirecIndexApiException {
		submitterService.setAutocommitactive(active);
		log.info(String.format("automatic commit, active '%s', frequency '%s'", submitterService.isAutocommitactive(), submitterService.getCommitfrquency()));
		return submitterService.isAutocommitactive();
	}

	@RequestMapping(value = "/api/admin/evictCache", method = RequestMethod.GET)
	@ResponseStatus(value = HttpStatus.OK)
	public void evictCache() {
		indexDSRetriever.evictCache();
	}

	@Deprecated
	@RequestMapping(value = {
		"/api/publications/feedJson", "/api/results/feedJson"
	}, method = RequestMethod.POST)
	public @ResponseBody String feedObjectJson(@RequestParam(value = "json", required = true) final String json,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit) throws DirecIndexApiException {
		log.debug(json);
		final ResultEntry pub = new Gson().fromJson(json, ResultEntry.class);
		return feedObject(pub, commit);
	}

	@RequestMapping(value = {
		"/api/results/feedObject"
	}, method = RequestMethod.POST)
	public @ResponseBody String feedResult(@RequestBody final ResultEntry pub,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit)
		throws DirecIndexApiException {
		return feed(pub, commit);

	}

	@Deprecated
	@RequestMapping(value = {
		"/api/publications/feedObject"
	}, method = RequestMethod.POST)
	public @ResponseBody String feedObject(@RequestBody final ResultEntry pub,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit)
		throws DirecIndexApiException {
		return feed(pub, commit);
	}

	@RequestMapping(value = "/api/result/{openaireId}", method = RequestMethod.DELETE)
	public @ResponseBody boolean deleteResultWithOpenaireId(
		@PathVariable(value = "openaireId") final String openaireId,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit) throws DirecIndexApiException {

		return deleteResult(openaireId);
	}

	@RequestMapping(value = "/api/results", method = RequestMethod.DELETE)
	public @ResponseBody boolean deleteResultWithOriginalId(
		@RequestParam(value = "originalId", required = true) final String originalId,
		@RequestParam(value = "collectedFromId", required = true) final String collectedFromId,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit) throws Exception {

		final String openaireId = ResultEntryToOaf.calculateOpenaireId(originalId, collectedFromId, utils);
		return deleteResult(openaireId);
	}

	@Deprecated
	@RequestMapping(value = {
		"/api/publications/deleteObject", "/api/results/delete"
	}, method = RequestMethod.POST)
	public @ResponseBody boolean deleteResultPost(
		@RequestParam(value = "originalId", required = true) final String originalId,
		@RequestParam(value = "collectedFromId", required = true) final String collectedFromId,
		@RequestParam(value = "commit", required = false, defaultValue = "true") final boolean commit) throws Exception {

		final String openaireId = ResultEntryToOaf.calculateOpenaireId(originalId, collectedFromId, utils);

		return deleteResult(openaireId);
	}

	@Deprecated
	private String feed(final ResultEntry pub, final boolean commit) throws DirecIndexApiException {
		return feed(pub);
	}

	private String feed(final ResultEntry pub) throws DirecIndexApiException {
		log.debug(pub);
		try {
			final ResultEntryToOaf toOaf = new ResultEntryToOaf(utils);
			final IndexDsInfo info = indexDSRetriever.calculateCurrentIndexDsInfo();
			final String oafRecord = toOaf.asOafRecord(pub, velocityEngine, serviceLocator.getService(ISLookUpService.class), oafSchemaLocation);
			final SolrInputDocument solrDocument =
				prepareSolrDocument(oafRecord, info.getIndexDsId(), oafToIndexRecordFactory.newTransformer(info.getFormat()));

			clientManager.getClient(info).add(solrDocument);
			recentResultsQueue.add(oafRecord);

			return pub.getOpenaireId();
		} catch (final Throwable e) {
			log.error("Error saving record", e);
			log.debug(pub.toString());
			throw new DirecIndexApiException("Error adding publication: " + e.getMessage(), e);
		}
	}

	private SolrInputDocument prepareSolrDocument(final String record, final String indexDsId, final UnaryFunction<String, String> toIndexRecord) throws DirecIndexApiException {
		try {
			final StreamingInputDocumentFactory documentFactory = new StreamingInputDocumentFactory();
			final String version = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'").format(new Date());
			final String indexRecord = toIndexRecord.evaluate(record);
			if (log.isDebugEnabled()) {
				log.debug("***************************************\nSubmitting index record:\n" + indexRecord + "\n***************************************\n");
			}

			return documentFactory.parseDocument(version, indexRecord, "dnetResult");
		} catch (final Throwable e) {
			throw new DirecIndexApiException("Error creating solr document", e);
		}
	}

	private boolean deleteResult(final String openaireId) throws DirecIndexApiException {
		try {
			final IndexDsInfo info = indexDSRetriever.calculateCurrentIndexDsInfo();
			final String query = String.format("objidentifier:\"%s\" OR resultdupid:\"%s\"", openaireId, openaireId);
			final CloudSolrClient client = clientManager.getClient(info);
			client.deleteByQuery(info.getColl(), query);
			log.info("Deleted result with id: " + openaireId + " from: " + info.getIndexBaseUrl());

			recentResultsQueue.remove(openaireId);
			return true;
		} catch (final Throwable e) {
			throw new DirecIndexApiException("Error deleting publication: " + e.getMessage(), e);
		}
	}

	@ExceptionHandler(Exception.class)
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public @ResponseBody ErrorMessage handleException(final Exception e) {
		log.error("Error in direct index API", e);
		return new ErrorMessage(e);
	}

	public class ErrorMessage {

		private final String message;
		private final String stacktrace;

		public ErrorMessage(final Exception e) {
			this(e.getMessage(), ExceptionUtils.getStackTrace(e));
		}

		public ErrorMessage(final String message, final String stacktrace) {
			this.message = message;
			this.stacktrace = stacktrace;
		}

		public String getMessage() {
			return this.message;
		}

		public String getStacktrace() {
			return this.stacktrace;
		}

	}

}
