package eu.dnetlib.services;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
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.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.google.common.collect.Sets;

import eu.dnetlib.clients.is.DnetSchemaId;
import eu.dnetlib.clients.is.InformationServiceClient;
import eu.dnetlib.enabling.annotations.DnetServiceType;
import eu.dnetlib.exceptions.InformationServiceException;

@Component
public class ServiceRegistrationManager {

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

	private static final String DEFAULT_SERVICE_KIND = "dnetService";

	@Autowired
	private InformationServiceClient isClient;

	private boolean disabled = false;

	@Autowired(required = false)
	private List<BaseService> services = new ArrayList<>();

	private Resource schemaTemplate = new ClassPathResource("/templates/ServiceSchemaTemplate.st");
	private Resource profileTemplate = new ClassPathResource("/templates/ServiceProfileTemplate.st");

	@Scheduled(initialDelay = 5000, fixedDelay = 20000)
	public void registerAllServices() {

		if (disabled) { return; }

		disabled = true;

		try {
			log.info("registering services...");

			final Set<DnetSchemaId> schemas = Sets.newHashSet(isClient.listSchemas());

			for (final BaseService service : services) {

				final DnetSchemaId schemaId = new DnetSchemaId(DEFAULT_SERVICE_KIND, service.getServiceType().toString());

				if (!schemas.contains(schemaId)) {
					final StringTemplate schema = new StringTemplate(IOUtils.toString(schemaTemplate.getInputStream()));

					schema.setAttribute("resourceType", schemaId.getType());

					isClient.registerSchema(schemaId, schema.toString());

					log.info(String.format("  schema %s registered", schemaId));
				} else {
					log.info(String.format("  schema %s already registered", schemaId));
				}

				registerOrUpdateService(service);
			}
			log.info("...all services registered");
		} catch (final Throwable e) {
			log.warn("...registration failed - " + e.getMessage());
			log.debug(e);
			disabled = false;
		}

	}

	private String findRegisteredService(final String url) throws InformationServiceException {

		final String query =
				"for $x in collection('/db/DRIVER/dnetService') where $x//RESOURCE_URI/@value='" + url + "' return $x//RESOURCE_IDENTIFIER/@value/string()";

		final List<String> profIds = isClient.find(query);

		return (profIds.isEmpty()) ? null : profIds.get(0);
	}

	private void registerOrUpdateService(final BaseService service) throws Exception {

		final DnetServiceType type = service.getServiceType();
		final String id = findRegisteredService(service.getBaseUrl());

		final StringTemplate templ = new StringTemplate(IOUtils.toString(profileTemplate.getInputStream()));
		templ.setAttribute("resourceType", type);
		templ.setAttribute("baseUrl", service.getBaseUrl());
		templ.setAttribute("properties", service.getServiceProperties());
		templ.setAttribute("anyXml", service.geXmlProfileSections().stream().map(Element::asXML).collect(Collectors.joining()));
		final String profile = templ.toString();

		log.debug(profile);

		if (id != null) {
			service.setProfileId(id);
			isClient.updateProfile(id, profile);
			log.info("  service " + service.getProfileId() + " updated");
		} else {
			final String profId = isClient.register(profile);
			service.setProfileId(profId);
			log.info("  service: " + service.getProfileId() + " registered");
		}
	}

	public boolean isDisabled() {
		return disabled;
	}

}
