package eu.dnetlib.functionality.modular.ui.repositories;

import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import eu.dnetlib.data.collector.rmi.CollectorService;
import eu.dnetlib.data.collector.rmi.CollectorServiceException;
import eu.dnetlib.data.collector.rmi.ProtocolParameterValue;
import eu.dnetlib.enabling.datasources.common.Api;
import eu.dnetlib.enabling.datasources.common.ApiParam;
import eu.dnetlib.enabling.datasources.common.ApiParamImpl;
import eu.dnetlib.enabling.datasources.common.BrowseTerm;
import eu.dnetlib.enabling.datasources.common.Datasource;
import eu.dnetlib.enabling.datasources.common.DsmException;
import eu.dnetlib.enabling.datasources.common.Identity;
import eu.dnetlib.enabling.datasources.common.LocalDatasourceManager;
import eu.dnetlib.enabling.datasources.common.Organization;
import eu.dnetlib.enabling.datasources.common.SearchApisEntry;
import eu.dnetlib.enabling.datasources.common.SimpleDatasource;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.functionality.modular.ui.error.ErrorMessage;
import eu.dnetlib.functionality.modular.ui.repositories.objects.RepoInterfaceEntry;
import eu.dnetlib.functionality.modular.ui.workflows.objects.sections.WorkflowSectionGrouper;
import eu.dnetlib.miscutils.functional.xml.ApplyXslt;
import eu.dnetlib.msro.workflows.sarasvati.loader.WorkflowExecutor;
import eu.dnetlib.msro.workflows.util.WorkflowsConstants;

@Controller
public class RepoInternalController {

	@Autowired
	private LocalDatasourceManager<Datasource<?, ?>, Api<?>> dsManager;

	@Resource
	private UniqueServiceLocator serviceLocator;

	@Resource
	private WorkflowSectionGrouper workflowSectionGrouper;

	@Resource
	private WorkflowExecutor workflowExecutor;

	@Resource
	private RepoUIUtils repoUIUtils;

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

	@RequestMapping(value = "/ui/browseRepoField.do")
	public @ResponseBody List<BrowseTerm> browseRepoField(@RequestParam(value = "field", required = true) final String field) throws Exception {
		return dsManager.browseField(field);
	}

	@Cacheable(cacheNames = "repoUIJsonCache", key = "#param, #value", condition = "#refresh == false")
	@RequestMapping(value = "/ui/listApis.do")
	public @ResponseBody List<? extends SearchApisEntry> listApis(
			@RequestParam(value = "param", required = true) final String param,
			@RequestParam(value = "value", required = true) final String value,
			@RequestParam(value = "refresh", required = false) final String refresh) throws Exception {

		return dsManager.searchApis(param, value);
	}

	@RequestMapping(value = "/ui/listRepositories.json")
	public @ResponseBody List<SimpleDatasource> listRepositories(@RequestParam(value = "type", required = true) final String type) throws Exception {
		return dsManager.searchDatasourcesByType(type);
	}

	@CacheEvict("repoUIJsonCache")
	@RequestMapping(value = "/ui/validateRepo.do")
	public @ResponseBody String listRepositories(@RequestParam(value = "id", required = true) final String id,
			@RequestParam(value = "b", required = true) final boolean b) throws Exception {

		final String query = "count(/*[.//RESOURCE_TYPE/@value='MetaWorkflowDSResourceType' and .//DATAPROVIDER/@id='" + id + "'])";
		if (!b && Integer.parseInt(serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(query)) > 0) { throw new Exception("Repo " + id
				+ " can be invalidated: it is related to some metawfs"); }

		final String newId = b ? serviceLocator.getService(ISRegistryService.class).validateProfile(id)
				: serviceLocator.getService(ISRegistryService.class).invalidateProfile(id);

		return newId;
	}

	@RequestMapping(value = "/ui/getRepoDetails.do")
	public void getRepoDetails(final HttpServletResponse response, @RequestParam(value = "id", required = true) final String id) throws Exception {
		String profile;
		try {
			profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(id);
		} catch (final ISLookUpDocumentNotFoundException e) {
			profile = serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(
					"collection('/db/DRIVER/RepositoryServiceResources/RepositoryServiceResourceType')/*[.//DATASOURCE_ORIGINAL_ID='" + id + "']");
		}

		final ApplyXslt xslt = new ApplyXslt(IOUtils.toString(getClass().getResourceAsStream(
				"/eu/dnetlib/functionality/modular/ui/repositories/xslt/repoDetails.xslt")));

		IOUtils.copy(new StringReader(xslt.evaluate(profile)), response.getOutputStream());
	}

	@RequestMapping("/ui/repoMetaWf.new")
	public @ResponseBody String newDataProviderWorkflow(@RequestParam(value = "id", required = true) final String repoId,
			@RequestParam(value = "name", required = true) final String repoName,
			@RequestParam(value = "iface", required = true) final String ifaceId,
			@RequestParam(value = "wf", required = true) final String wfId) throws Exception {

		final Map<String, Object> params = Maps.newHashMap();
		params.put(WorkflowsConstants.DATAPROVIDER_ID, repoId);
		params.put(WorkflowsConstants.DATAPROVIDER_NAME, repoName);
		params.put(WorkflowsConstants.DATAPROVIDER_INTERFACE, ifaceId);

		return workflowExecutor.startProcess(wfId, params);
	}

	@RequestMapping("/ui/repoMetaWf.destroy")
	public @ResponseBody String destroyDataProviderWorkflow(@RequestParam(value = "destroyWf", required = true) final String destroyWfId)
			throws Exception {
		return workflowExecutor.startProcess(destroyWfId, null);
	}

	@RequestMapping("/ui/repoApi.get")
	public @ResponseBody RepoInterfaceEntry getRepoApi(@RequestParam(value = "repoId", required = true) final String repoId,
			@RequestParam(value = "ifaceId", required = true) final String ifaceId) throws Exception {

		try {
			return repoUIUtils.getApi(repoId, ifaceId);
		} catch (ISLookUpDocumentNotFoundException e) {

			log.warn(String.format("the Interface '%s' is not available for repository '%s', try to sync DB and profiles via the DatasourceManager", ifaceId, repoId));
			dsManager.setActive(repoId, ifaceId, dsManager.isActive(repoId, ifaceId));
			return repoUIUtils.getApi(repoId, ifaceId);
		}
	}

	@RequestMapping("/ui/repoApi.update")
	public @ResponseBody boolean updateRepoApi(
			@RequestParam(value = "id", required = true) final String repoId,
			@RequestParam(value = "iface", required = true) final String ifaceId,
			@RequestParam(value = "accessParams", required = false) final String accessParamsJson,
			@RequestParam(value = "mdIdPath", required = false) final String mdIdPath) throws Exception {

		if (!StringUtils.isEmpty(accessParamsJson)) {
			final Map<String, String> params = new Gson().fromJson(accessParamsJson, new TypeToken<Map<String, String>>() {}.getType());
			final String baseUrl = params.remove("baseUrl");
			dsManager.updateApiDetails(repoId, ifaceId, mdIdPath, baseUrl, params);
		}

		return true;
	}

	@RequestMapping("/ui/repoApi.delete")
	public @ResponseBody boolean updateRepoApi(
			@RequestParam(value = "repo", required = true) final String repoId,
			@RequestParam(value = "iface", required = true) final String ifaceId) throws Exception {
		dsManager.deleteApi(repoId, ifaceId);
		return true;
	}

	@CacheEvict("repoUIJsonCache")
	@RequestMapping("/ui/repoApiCompliance.update")
	public @ResponseBody boolean updateRepoApiCompliance(@RequestParam(value = "id", required = true) final String repoId,
			@RequestParam(value = "iface", required = true) final String ifaceId,
			@RequestParam(value = "compliance", required = true) final String compliance) throws Exception {

		log.debug("SET COMPLIANCE TO " + compliance);

		dsManager.updateCompliance(repoId, ifaceId, compliance, true);

		return true;
	}

	@CacheEvict("repoUIJsonCache")
	@RequestMapping("/ui/repoApiCompliance.reset")
	public @ResponseBody boolean resetRepoApiCompliance(@RequestParam(value = "id", required = true) final String repoId,
			@RequestParam(value = "iface", required = true) final String ifaceId) throws Exception {

		log.debug("RESET COMPLIANCE");

		dsManager.updateCompliance(repoId, ifaceId, null, true);

		return true;
	}

	@RequestMapping("/ui/repos/repoApi.html")
	public void resetRepoApiCompliance(final ModelMap map) throws Exception {}

	@RequestMapping("/ui/repoApi.new")
	public @ResponseBody boolean addRepoApi(@RequestParam(value = "repoId", required = true) final String repoId,
			@RequestParam(value = "iface", required = true) final String ifaceJson) throws DsmException {
		final Api<ApiParam> iface = new Gson().fromJson(ifaceJson, new TypeToken<Api<ApiParamImpl>>() {}.getType());

		iface.setDatasource(repoId);

		log.info("Adding api " + iface.getId() + " to repository " + repoId);

		dsManager.addApi(iface);

		return true;
	}

	@RequestMapping("/ui/repo.new")
	public @ResponseBody boolean addRepoApi(@RequestParam(value = "repo", required = true) final String repoJson) throws DsmException {
		final Datasource<Organization<?>, Identity> ds = new Gson().fromJson(repoJson, new TypeToken<Datasource<Organization<?>, Identity>>() {}.getType());
		final Date now = new Date();
		ds.setDateofcollection(new java.sql.Date(now.getTime()));

		if (StringUtils.isBlank(ds.getEnglishname())) {
			ds.setEnglishname(ds.getOfficialname());
		}

		log.info("Adding datasource " + ds.getId() + " - name " + ds.getOfficialname());

		dsManager.saveDs(ds);

		return true;
	}

	@RequestMapping("/ui/listValidValuesForParam.do")
	public @ResponseBody List<ProtocolParameterValue> listValidValuesForParam(
			@RequestParam(value = "protocol", required = true) final String protocol,
			@RequestParam(value = "param", required = true) final String param,
			@RequestParam(value = "baseUrl", required = true) final String baseUrl) throws CollectorServiceException {

		return serviceLocator.getService(CollectorService.class).listValidValuesForParam(protocol, baseUrl, param, null);
	}

	@ExceptionHandler(Exception.class)
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public @ResponseBody ErrorMessage handleException(final HttpServletRequest req, final Exception e) {
		log.error("Error processing " + req.getRequestURI(), e);
		return new ErrorMessage(e.getMessage(), ExceptionUtils.getStackTrace(e));
	}

}
