package eu.dnetlib.openaire.directindex.objects;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.ui.velocity.VelocityEngineUtils;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.miscutils.datetime.DateUtils;
import eu.dnetlib.miscutils.functional.hash.Hashing;
import eu.dnetlib.miscutils.functional.string.EscapeXml;
import eu.dnetlib.openaire.directindex.api.DirecIndexApiException;
import eu.dnetlib.openaire.directindex.api.OpenAIRESubmitterUtils;

public class ResultEntryToOaf {

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

	private static long last_cache_update = 0;
	private static final Map<String, Map<String, String>> cached_vocabularies = new HashMap<>();
	private static final Map<String, DatasourceEntry> cached_datasources = new HashMap<>();
	private static final Map<String, String> cached_contexts = new HashMap<>();

	private static final String ZENODO_ID="opendoar____::2659";
	private static final String DOI_PREFIX = "doi_________";

	private OpenAIRESubmitterUtils utils;

	public ResultEntryToOaf(final OpenAIRESubmitterUtils utils) {
		this.utils = utils;
	}

	public String asOafRecord(final ResultEntry entry,
		final VelocityEngine ve,
		final ISLookUpService lookupService,
		final String oafSchemaLocation) throws Exception {

		if (StringUtils.isBlank(entry.getOriginalId())
			&& StringUtils
				.isBlank(entry.getOpenaireId())) {
			throw new DirecIndexApiException("One of the following fields is required: originalId or openaireId");
		}
		if (StringUtils.isBlank(entry.getTitle())) { throw new DirecIndexApiException("A required field is missing: title"); }
		if (StringUtils.isBlank(entry.getUrl())) { throw new DirecIndexApiException("A required field is missing: url"); }
		if (StringUtils.isBlank(entry.getLicenseCode()) && StringUtils.isBlank(entry.getAccessRightCode())) {
			throw new DirecIndexApiException("A required field is missing: accessRightCode");
		}
		if (StringUtils.isBlank(entry.getResourceType())) { throw new DirecIndexApiException("A required field is missing: resourceType"); }
		if (StringUtils.isBlank(entry.getCollectedFromId())) { throw new DirecIndexApiException("A required field is missing: collectedFromId"); }
		if (StringUtils.isBlank(entry.getType())) { throw new DirecIndexApiException("A required field is missing: type"); }

		final DatasourceEntry collectedFromEntry = getDatasourceInfo(entry.getCollectedFromId(), utils);
		final DatasourceEntry hostedByEntry = getDatasourceInfo(entry.getHostedById(), utils);

		if (StringUtils.isBlank(entry.getOpenaireId())) {
			if(entry.getCollectedFromId().equals(ZENODO_ID)){
				String doi = entry.getPids().stream().filter(e -> e.getType().equalsIgnoreCase("doi")).findFirst().get().getValue();
				entry.setOpenaireId(calculateOpenaireId(DOI_PREFIX, doi));
			}
			else entry.setOpenaireId(calculateOpenaireId(entry.getOriginalId(), collectedFromEntry));
		}

		if (!entry.getOpenaireId()
			.matches("^\\w{12}::\\w{32}$")) {
			throw new DirecIndexApiException(
				"Invalid openaireId: " + entry.getOpenaireId() + " - regex ^\\w{12}::\\w{32}$ not matched");
		}

		final List<OpenAIRESubmitterUtils.ContextInfo> contextInfos = utils.processContexts(entry.getContexts(), getContexts(lookupService));

		final Map<String, Object> model = new HashMap<>();
		model.put("esc", new EscapeXml());
		model.put("util", utils);
		model.put("pub", entry);
		model.put("objIdentifier", entry.getOpenaireId());
		model.put("oafSchemaLocation", oafSchemaLocation);
		model.put("resultTypes", getVocabulary("dnet:result_typologies", lookupService));
		model.put("rights", getVocabulary("dnet:access_modes", lookupService));
		model.put("resourceTypes", getVocabulary("dnet:publication_resource", lookupService));
		model.put("pidTypes", getVocabulary("dnet:pid_types", lookupService));
		model.put("languages", getVocabulary("dnet:languages", lookupService));
		model.put("contexts", getContexts(lookupService));
		model.put("contextInfo", contextInfos);
		model.put("dateOfCollection", new SimpleDateFormat("yyyy-MM-dd\'T\'hh:mm:ss\'Z\'").format(new Date()));
		model.put("collectedFrom", collectedFromEntry);
		model.put("hostedBy", hostedByEntry);

		return VelocityEngineUtils.mergeTemplateIntoString(ve, "/eu/dnetlib/openaire/directindex/indexRecord.xml.vm", "UTF-8", model);
	}

	private synchronized static DatasourceEntry getDatasourceInfo(final String dsId, final OpenAIRESubmitterUtils utils) throws ISLookUpException {
		if (StringUtils
			.isBlank(dsId)) {
			return new DatasourceEntry("openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18", "Unknown Repository", "unknown_____");
		}

		if (!cached_datasources.containsKey(dsId)) {

			final DatasourceEntry ds = utils.findDatasource(dsId);

			if (ds == null || StringUtils.isBlank(ds.getName()) || StringUtils.isBlank(ds.getPrefix())) {
				log.error("Invalid datasource id: " + dsId);
				throw new ISLookUpException("Invalid datasource id: " + dsId);
			} else {
				cached_datasources.put(dsId, ds);
			}
		}

		return cached_datasources.get(dsId);

	}

	private synchronized static Map<String, String> getVocabulary(final String voc, final ISLookUpService lookupService) throws ISLookUpException {

		if (DateUtils.now() - last_cache_update < TimeUnit.MINUTES.toMillis(15) && cached_vocabularies.containsKey(voc)) {
			return cached_vocabularies.get(voc);
		} else {
			final String query = "collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType')[.//VOCABULARY_NAME/@code='" + voc
				+ "']//TERM/concat(@code, ' @@@ ', @english_name)";

			final Map<String, String> map = new HashMap<>();
			for (final String s : lookupService.quickSearchProfile(query)) {
				final String[] arr = s.split("@@@");
				map.put(arr[0].trim(), arr[1].trim());
			}

			cached_vocabularies.put(voc, map);

			last_cache_update = DateUtils.now();

			return map;
		}
	}

	private synchronized static Map<String, String> getContexts(final ISLookUpService lookupService) throws ISLookUpException {
		if (DateUtils.now() - last_cache_update > TimeUnit.MINUTES.toMillis(15) || cached_contexts.isEmpty()) {
			final String query =
				"collection('/db/DRIVER/ContextDSResources/ContextDSResourceType')[.//context/@type='community' or .//context/@type='ri']//*[name()='context' or name()='category' or name()='concept']/concat(@id, ' @@@ ', @label)";

			cached_contexts.clear();
			for (final String s : lookupService.quickSearchProfile(query)) {
				final String[] arr = s.split("@@@");
				cached_contexts.put(arr[0].trim(), arr[1].trim());
			}
			last_cache_update = DateUtils.now();
		}
		return cached_contexts;
	}
	private static String calculateOpenaireId(final String prefix, final String id) {
		return prefix + "::" + Hashing.md5(id);
	}

	private static String calculateOpenaireId(final String originalId, final DatasourceEntry collectedFromEntry) {
		return collectedFromEntry.getPrefix() + "::" + Hashing.md5(originalId);
	}

	public static String calculateOpenaireId(final String originalId, final String collectedFromId, final OpenAIRESubmitterUtils utils)
		throws ISLookUpException {
		return calculateOpenaireId(originalId, getDatasourceInfo(collectedFromId, utils));
	}

	public OpenAIRESubmitterUtils getUtils() {
		return utils;
	}

	public void setUtils(final OpenAIRESubmitterUtils utils) {
		this.utils = utils;
	}
}
