package eu.dnetlib.msro;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
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.RestController;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import eu.dnetlib.clients.is.InformationServiceClient;
import eu.dnetlib.clients.msro.Workflow;
import eu.dnetlib.clients.msro.WorkflowTemplate;
import eu.dnetlib.enabling.annotations.DnetService;
import eu.dnetlib.enabling.annotations.DnetServiceType;
import eu.dnetlib.exceptions.InformationServiceException;
import eu.dnetlib.msro.exceptions.MSROException;
import eu.dnetlib.msro.workflows.GraphNodeParameter;
import eu.dnetlib.msro.workflows.WorkflowRef;
import eu.dnetlib.msro.workflows.util.GraphLoader;
import eu.dnetlib.msro.workflows.util.WorkflowDispatcher;
import eu.dnetlib.msro.workflows.util.logs.WorkflowLog;
import eu.dnetlib.msro.workflows.util.logs.WorkflowLogRepository;
import eu.dnetlib.services.BaseService;
import eu.dnetlib.services.async.AsyncMethodException;
import eu.dnetlib.services.async.AsyncRunnable;
import eu.dnetlib.services.async.AsyncServerCallback;
import eu.dnetlib.services.async.HasAsyncMethods;

@RestController
@RequestMapping("/msro")
@DnetService(DnetServiceType.msro)
public class MsroController extends BaseService implements HasAsyncMethods {

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

	@Autowired
	private WorkflowDispatcher dispatcher;

	@Autowired
	private WorkflowLogRepository wfLogRepository;

	@Autowired
	private GraphLoader graphLoader;

	@Autowired
	private InformationServiceClient isClient;

	@RequestMapping("startWorkflow")
	public WorkflowRef startWorkflow(
			@RequestParam final String wfId,
			@RequestParam(required = false) final String parent,
			@RequestParam(required = false) final String dsId,
			@RequestParam(required = false) final String ifaceId) throws MSROException {

		return dispatcher.startWorkflow(wfId, parent, dsId, ifaceId, getBaseUrl(), null);

	}

	@RequestMapping(value = "startWorkflowTemplate", method = RequestMethod.GET)
	public WorkflowRef startWorkflowTemplate(@RequestParam final String parent, @RequestParam final String node) throws MSROException {

		try {
			final Document docParent = (new SAXReader()).read(new StringReader(isClient.getProfile(parent)));
			final String family = docParent.valueOf("//WORKFLOW_FAMILY");
			final String dsId = docParent.valueOf("//DATASOURCE/@id");
			final String iface = docParent.valueOf("//DATASOURCE/@interface");

			final String workerId = docParent.valueOf("//RESOURCE_MANAGER/@value");

			final Map<String, String> globalParams = new HashMap<>();
			for (final Object o : docParent.selectNodes("//CONFIGURATION/PARAMETERS/PARAM")) {
				final Element p = (Element) o;
				globalParams.put(p.valueOf("@name"), p.getTextTrim());
			}
			final Map<String, GraphNodeParameter> mapParams =
					graphLoader.calculateParamsForNode(docParent.selectSingleNode("//NODE[@name='" + node + "']"), globalParams);

			final String wfTemplateId = mapParams.get("wfTemplateId").getValue();
			final Map<String, String> params = new HashMap<>();

			final Map<String, GraphNodeParameter> unresolved =
					(new ObjectMapper()).readValue(mapParams.get("wfTemplateParams").getValue(), new TypeReference<Map<String, GraphNodeParameter>>() {});

			for (final Map.Entry<String, GraphNodeParameter> entry : unresolved.entrySet()) {
				final String k = entry.getKey();
				if (entry.getValue().isEnvParam()) {
					log.error("Error launching a template containing ENV parmaters");
					throw new MSROException("Error launching a template containing ENV parmaters");
				} else {
					params.put(k, entry.getValue().getValue());
				}

			}

			final WorkflowTemplate wfTemplate = new WorkflowTemplate(wfTemplateId, parent, node, family, dsId, iface, params, workerId);

			return startWorkflowTemplate(wfTemplate);
		} catch (final IOException | InformationServiceException | DocumentException e) {
			log.error("Error executing wfTemplate: " + node, e);
			throw new MSROException("Error executing wfTemplate: " + node, e);
		}

	}

	@RequestMapping(value = "startWorkflowTemplate", method = RequestMethod.POST)
	public WorkflowRef startWorkflowTemplate(@RequestBody final WorkflowTemplate wf) throws MSROException {
		return dispatcher.startWorkflowTemplate(wf, getBaseUrl(), null);
	}

	@RequestMapping("startRepoHiWorkflow")
	public WorkflowRef startRepoHiWorkflow(
			@RequestParam final String wfId,
			@RequestParam final String workerId,
			@RequestParam final String dsId,
			@RequestParam final String iface) throws MSROException {
		return dispatcher.startRepoHiWorkflow(wfId, workerId, dsId, iface, getBaseUrl());
	}

	@RequestMapping("startRepoByeWorkflow")
	public WorkflowRef startRepoByeWorkflow(@RequestParam final String wfId) throws MSROException {
		return dispatcher.startRepoByeWorkflow(wfId, getBaseUrl());
	}

	@RequestMapping("logs")
	public List<WorkflowLog> logs(@RequestParam final String wfId) {
		return wfLogRepository.findByProfileId(wfId);
	}

	@Override
	public AsyncRunnable createAsyncJob(final String method, final String jsonParams, final AsyncServerCallback callback) throws AsyncMethodException {
		final ObjectMapper mapper = new ObjectMapper();

		return new AsyncRunnable() {

			@Override
			public Map<String, String> prepare() {
				return new HashMap<>();
			}

			@Override
			public void execute() throws AsyncMethodException {
				try {
					switch (method) {
					case "startWorkflow":
						dispatcher.startWorkflow(mapper.readValue(jsonParams, Workflow.class), getBaseUrl(), callback);
						break;
					case "startWorkflowTemplate":
						dispatcher.startWorkflowTemplate(mapper.readValue(jsonParams, WorkflowTemplate.class), getBaseUrl(), callback);
						break;
					default:
						log.warn("Invalid method: " + method);
						throw new AsyncMethodException("Invalid method: " + method);
					}
				} catch (final Throwable e) {
					log.warn("Error executing method: " + method, e);
					throw new AsyncMethodException("Error executing method: " + method, e);
				}
			}
		};

	}

}
