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

import java.io.IOException;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.xml.transform.dom.DOMResult;
import javax.xml.xpath.XPathFactory;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.is.sn.SubscriptionRegistry;
import eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateSubscription;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.functionality.modular.ui.AbstractAjaxController;
import eu.dnetlib.functionality.modular.ui.is.bulk.ProfileImporter;
import eu.dnetlib.functionality.modular.ui.is.objects.*;
import eu.dnetlib.functionality.modular.ui.is.objects.ServiceDesc.ServiceStatus;
import eu.dnetlib.miscutils.datetime.DateUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class InformationServiceInternalController extends AbstractAjaxController {

	@Resource
	private UniqueServiceLocator serviceLocator;

	@Resource(name = "modularUiProfileImporter")
	private ProfileImporter profileImporter;

	/**
	 * is sn subscription registries.
	 */
	@Resource(name = "issResourceStateNotificationRegistry")
	private transient SubscriptionRegistry registry;

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

	@RequestMapping("/ui/is/xquery.do")
	public @ResponseBody
	List<String> query(@RequestParam(value = "query", required = true) final String query) throws Exception {
		log.debug("Executing xquery: " + query);
		return serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);
	}

	@RequestMapping("/ui/is/listSchemas.do")
	public @ResponseBody
	List<String> listSchemas() throws Exception {
		return serviceLocator.getService(ISLookUpService.class).listResourceTypes();
	}

	@RequestMapping("/ui/is/getSchema.do")
	public @ResponseBody
	String getSchema(@RequestParam(value = "name", required = true) final String name) throws Exception {
		return serviceLocator.getService(ISLookUpService.class).getResourceTypeSchema(name);
	}

	@RequestMapping("/ui/is/listCollections.do")
	public @ResponseBody
	List<CollectionDesc> listCollections() throws Exception {
		final String xquery = "for $kind in xmldb:get-child-collections('/db/DRIVER') " +
				"for $type in xmldb:get-child-collections(concat('/db/DRIVER/', $kind)) " +
				"return concat ($kind, ' @@@ ', $type, ' @@@ ', count(xmldb:get-child-resources(concat('/db/DRIVER/', $kind, '/', $type))))";

		final Map<String, CollectionDesc> map = Maps.newHashMap();
		for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery)) {
			final String[] arr = s.split("@@@");
			final String kind = arr[0].trim();
			final String type = arr[1].trim();
			final int size = NumberUtils.toInt(arr[2].trim(), 0);
			if (!map.containsKey(kind)) {
				map.put(kind, new CollectionDesc(kind));
			}
			map.get(kind).addType(type, size);
		}

		final List<CollectionDesc> res = Lists.newArrayList(map.values());
		for (CollectionDesc d : res) {
			Collections.sort(d.getTypes());
		}
		Collections.sort(res);

		return res;
	}

	@RequestMapping("/ui/is/listProfiles.do")
	public @ResponseBody
	List<String> listProfiles(@RequestParam(value = "kind", required = true) final String kind,
			@RequestParam(value = "type", required = true) final String type) throws Exception {
		final String collName = "/db/DRIVER/" + kind + "/" + type;
		final String xquery = "distinct-values(for $x in collection('" + collName + "') return $x//RESOURCE_IDENTIFIER/@value/string())";
		final List<String> res = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery);

		Collections.sort(res);

		return res;
	}

	@RequestMapping("/ui/is/getProfile.do")
	public @ResponseBody
	String getProfiles(@RequestParam(value = "id", required = true) final String id) throws Exception {
		return serviceLocator.getService(ISLookUpService.class).getResourceProfile(id);
	}

	@RequestMapping("/ui/is/registerProfile.do")
	public @ResponseBody
	String registerProfile(@RequestParam(value = "profile", required = true) final String profile) throws Exception {
		return serviceLocator.getService(ISRegistryService.class).registerProfile(profile);
	}

	@RequestMapping("/ui/is/updateProfile.do")
	public @ResponseBody
	String updateProfile(@RequestParam(value = "profile", required = true) final String profile) throws Exception {
		final SAXReader reader = new SAXReader();
		final Document doc = reader.read(new StringReader(profile));

		final String id = doc.valueOf("//RESOURCE_IDENTIFIER/@value");
		final String type = doc.valueOf("//RESOURCE_TYPE/@value");

		if (StringUtils.isEmpty(id)) {
			throw new Exception("RESOURCE_IDENTIFIER is empty");
		} else if (StringUtils.isEmpty(type)) {
			throw new Exception("RESOURCE_TYPE is empty");
		} else if (serviceLocator.getService(ISRegistryService.class).updateProfile(id, profile, type)) {
			return id;
		} else {
			throw new Exception("Profile not updated");
		}
	}

	@RequestMapping("/ui/is/deleteProfile.do")
	public @ResponseBody
	boolean deleteProfile(@RequestParam(value = "id", required = true) final String id) throws Exception {
		return serviceLocator.getService(ISRegistryService.class).deleteProfile(id);
	}

	@RequestMapping("/ui/is/import.do")
	public @ResponseBody
	Map<String, Integer> importProfiles(
			@RequestParam(value = "path", required = true) final String path,
			@RequestParam(value = "profiles", required = true) final boolean profiles,
			@RequestParam(value = "schemas", required = true) final boolean schemas) throws Exception {

		log.info("importing profiles/schemas from " + path);

		final Map<String, Integer> res = Maps.newHashMap();
		if (schemas) {
			res.putAll(profileImporter.importSchemas(path + "/**/*.xsd"));
		}
		if (profiles) {
			res.putAll(profileImporter.importProfiles(path + "/**/*.xml"));
		}

		return res;
	}

	@RequestMapping("/ui/is/listBlackboards.do")
	public @ResponseBody
	List<BlackboardMessage> listBlackboards() throws Exception {
		final List<BlackboardMessage> list = Lists.newArrayList();

		final SAXReader reader = new SAXReader();

		for (String xml : serviceLocator
				.getService(ISLookUpService.class)
				.quickSearchProfile(
						"for $x in collection('/db/DRIVER/ServiceResources')//MESSAGE return <message>{$x/../../..//RESOURCE_TYPE}{$x/../../..//RESOURCE_IDENTIFIER}{$x}</message>")) {
			final BlackboardMessage info = new BlackboardMessage();
			final Document doc = reader.read(new StringReader(xml));
			info.setProfId(doc.valueOf(".//RESOURCE_IDENTIFIER/@value"));
			info.setMessageId(doc.valueOf(".//@id"));
			info.setResourceType(doc.valueOf(".//RESOURCE_TYPE/@value"));
			info.setAction(doc.valueOf(".//ACTION"));
			info.setDate(doc.valueOf(".//@date"));
			info.setActionStatus(doc.valueOf(".//ACTION_STATUS"));
			info.setError(doc.valueOf(".//PARAMETER[@name='error']/@value"));
			list.add(info);
		}
		return list;
	}

	@RequestMapping("/ui/is/getMetaWfIdForFamily.do")
	public @ResponseBody
	Map<String, String> getMetaWfId(@RequestParam(value = "family", required = true) final String family) throws ISLookUpException {
		final String xq = "for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') " +
				"where $x//WORKFLOW_FAMILY='" + family + "' " +
				"return concat($x//RESOURCE_IDENTIFIER/@value, ' @@@ ', $x//WORKFLOW_NAME/@menuSection)";



		final Map<String, String> map = Maps.newHashMap();
		try {
			final String[] arr = serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(xq).split("@@@");
			map.put("id", arr[0].trim());
			map.put("section", arr[1].trim());
		} catch (ISLookUpDocumentNotFoundException e) {
			map.put("id", "");
			map.put("section", "");
		}
		map.put("family", family);

		return map;
	}

	@RequestMapping("/ui/is/listServices.do")
	public @ResponseBody
	Collection<ServiceGrouperDesc> listServices() throws Exception {
		final String xq = IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/functionality/modular/xquery/listServices.xquery"));

		final List<ServiceDesc> list = Lists.newArrayList();

		final SAXReader reader = new SAXReader();
		for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xq)) {
			final Document doc = reader.read(new StringReader(s));
			final String id = doc.valueOf("/service/id").trim();
			final String name = doc.valueOf("/service/name").trim();
			final String wsdl = doc.valueOf("/service/wsdl").trim();
			list.add(new ServiceDesc(id, name, wsdl));
		}

		final XPathFactory xpathFactory = XPathFactory.newInstance();

		for (ResourceStateSubscription sub : registry.listSubscriptions()) {
			boolean notFound = true;
			final DOMResult result = new DOMResult(); // NOPMD
			sub.getSubscriberAsEpr().writeTo(result);
			final String wsdl = xpathFactory.newXPath().evaluate("//*[local-name() = 'Address']", result.getNode()) + "?wsdl";

			for (ServiceDesc s : list) {
				if (s.getWsdl().equalsIgnoreCase(wsdl)) {
					s.getSubscriptions().add(new SubscriptionDesc(sub));
					notFound = false;
				}
			}
			if (notFound) {
				final ServiceDesc desc = new ServiceDesc("", "", wsdl);
				desc.getSubscriptions().add(new SubscriptionDesc(sub));
				desc.setStatus(ServiceStatus.MISSING);
				list.add(desc);
			}
		}

		final Map<String, ServiceGrouperDesc> map = Maps.newHashMap();
		for (ServiceDesc s : list) {
			final URL url = new URL(s.getWsdl());
			final String host = url.getHost();
			final int port = url.getPort();
			final String context = Iterables.getFirst(Splitter.on("/").omitEmptyStrings().split(url.getPath()), "");
			final String tmpKey = host + "@@@" + port + "@@@" + context;
			if (!map.containsKey(tmpKey)) {
				map.put(tmpKey, new ServiceGrouperDesc(host, port, context, new ArrayList<ServiceDesc>()));
			}
			map.get(tmpKey).getServices().add(s);
		}

		return map.values();
	}

	@RequestMapping("/ui/is/ping.do")
	public @ResponseBody
	long pingUrl(@RequestParam(value = "url", required = true) final String url, @RequestParam(value = "timeout", required = true) final int timeout)
			throws IOException {

		final long start = DateUtils.now();

		final HttpURLConnection urlConn = (HttpURLConnection) new URL(url).openConnection();
		urlConn.setConnectTimeout(timeout);
		urlConn.setReadTimeout(timeout);

		if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
			return DateUtils.now() - start;
		} else {
			throw new IllegalArgumentException("Invalid Url");
		}
	}

	public void sendXML(final ServletResponse response, final String xml) throws IOException {
		response.setContentType("text/xml");

		final ServletOutputStream out = response.getOutputStream();
		IOUtils.copy(new StringReader(xml), out);

		out.flush();
		out.close();
	}

}
