package eu.dnetlib.services;

import java.io.File;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.Lifecycle;
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.RestController;
import org.springframework.web.client.RestTemplate;

import eu.dnetlib.conf.DnetGenericApplicationProperties;
import eu.dnetlib.enabling.annotations.DnetService;
import eu.dnetlib.enabling.annotations.DnetServiceType;
import eu.dnetlib.exceptions.DnetGenericException;
import eu.dnetlib.services.async.AsyncClientUtils;
import eu.dnetlib.services.async.AsyncResponse;
import eu.dnetlib.services.async.AsyncRunnable;
import eu.dnetlib.services.async.AsyncServerCallback;
import eu.dnetlib.services.async.HasAsyncMethods;
import eu.dnetlib.services.async.ResponseAck;
import eu.dnetlib.services.async.ResponseAck.ResponseAckStatus;

public abstract class BaseService implements Lifecycle {

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

	private boolean started = false;

	private String profileId;

	private String baseUrl;

	private Map<String, String> serviceProperties = new HashMap<>();

	private Map<String, String> extraProtocols = new HashMap<>();

	private DnetServiceType serviceType;

	@Autowired
	private DnetGenericApplicationProperties containerConfiguration;

	@Autowired
	private AsyncClientUtils asyncClientUtils;

	@PostConstruct
	public void init() throws DnetGenericException {

		verifyRequiredAnnotations(DnetService.class);
		verifyRequiredAnnotations(RestController.class);
		verifyRequiredAnnotations(RequestMapping.class);

		serviceType = getClass().getAnnotation(DnetService.class).value();
		baseUrl = containerConfiguration.getUrl() + getClass().getAnnotation(RequestMapping.class).value()[0];

		final File dir = new File(containerConfiguration.getBaseDir());
		if (!dir.exists()) {
			dir.mkdirs();
		}
	}

	private void verifyRequiredAnnotations(final Class<? extends Annotation> a) throws DnetGenericException {
		if (!getClass().isAnnotationPresent(a)) {
			final String message = "A required annotation is missing (@" + a.getSimpleName() + ") in class " + getClass();
			log.error(message);
			throw new DnetGenericException(message);
		}
	}

	@Override
	public void start() {
		log.info("Starting service " + serviceType);
		if (started) {
			log.warn("Service " + this + "already started, check bean initializations!");
		}
		started = true;
	}

	@Override
	public boolean isRunning() {
		log.debug("called isRunning " + this);
		return false;
	}

	@Override
	public void stop() {
		log.info("Stopping service " + this);
	}

	@RequestMapping(value = "", method = RequestMethod.GET)
	public ServiceRunningInstance getStatus() {
		return ServiceRunningInstance.newInstance(profileId, baseUrl, baseDir());
	}

	@RequestMapping(value = "async/response/{id}", method = RequestMethod.POST)
	public final ResponseAck asyncResponse(@PathVariable final String id, @RequestBody final AsyncResponse response) {
		return asyncClientUtils.processResponse(id, response) ? ResponseAck.OK : ResponseAck.IGNORED;
	}

	@RequestMapping(value = "async/method/{method}", method = RequestMethod.POST)
	public final AsyncInvocation asyncMethod(@PathVariable final String method,
			@RequestParam(required = false) final String caller,
			@RequestBody final String jsonParams)
			throws DnetGenericException {

		log.info("Starting async method: " + method);

		if (this instanceof HasAsyncMethods) {

			final AsyncInvocation async = new AsyncInvocation("invocation-" + UUID.randomUUID());

			log.info("Async invocation:" + async.getId() + ", caller: " + caller);

			final AsyncServerCallback callback =
					StringUtils.isNotBlank(caller) ? res -> sendResponse(caller + "/async/response/" + async.getId(), res) : res -> {};

			final AsyncRunnable runnable = ((HasAsyncMethods) this).createAsyncJob(method, jsonParams, callback);

			async.setInfo(runnable.prepare());

			(new Thread(runnable)).start();

			return async;

		} else {
			log.error("Service " + getServiceType() + " does not implement HasAsyncMethods interface");
			throw new DnetGenericException("Service " + getServiceType() + " does not implement HasAsyncMethods interface");
		}

	}

	private ResponseAckStatus sendResponse(final String url, final AsyncResponse res) {
		log.info("***************************************************");
		log.info("* Sending response to : " + url);
		log.info("* Status              : " + res.getStatus());
		log.info("* Message             : " + res.getResponseJson());
		log.info("***************************************************");

		return (new RestTemplate()).postForObject(url, res, ResponseAck.class).getStatus();
	}

	protected String baseDir() {
		return containerConfiguration.getBaseDir();
	}

	public String getProfileId() {
		return profileId;
	}

	public void setProfileId(final String profileId) {
		this.profileId = profileId;
	}

	public Map<String, String> getServiceProperties() {
		return serviceProperties;
	}

	public List<Element> geXmlProfileSections() {
		return new ArrayList<>();
	}

	public void setServiceProperties(final Map<String, String> serviceProperties) {
		this.serviceProperties = serviceProperties;
	}

	public Map<String, String> getExtraProtocols() {
		return extraProtocols;
	}

	public void setExtraProtocols(final Map<String, String> extraProtocols) {
		this.extraProtocols = extraProtocols;
	}

	public DnetServiceType getServiceType() {
		return serviceType;
	}

	public void setServiceType(final DnetServiceType serviceType) {
		this.serviceType = serviceType;
	}

	public String getBaseUrl() {
		return baseUrl;
	}

	public void setBaseUrl(final String baseUrl) {
		this.baseUrl = baseUrl;
	}

}
