/*-
 * ~~~~~~licensing~~~~~~
 * schema-impl
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.platform.runtime.core.infrastructure.schema.impl;

import java.io.InputStream;
import java.util.List;
import java.util.Map;

import javax.wsdl.BindingOperation;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import javax.xml.namespace.QName;

import org.apache.cxf.BusException;
import org.apache.cxf.wsdl11.WSDLManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import ru.entaxy.esb.resources.EntaxyResource;
import ru.entaxy.esb.resources.EntaxyResourceMetadata;
import ru.entaxy.esb.resources.EntaxyResourceMetadata.MetadataSection;
import ru.entaxy.esb.resources.EntaxyResourceProcessor;
import ru.entaxy.platform.base.objects.EntaxyObject;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.core.producer.api.EntaxyProducerUtils;
import ru.entaxy.platform.core.producer.api.ProducerResult;

public class WSDLResourceProcessor implements EntaxyResourceProcessor {

	private static final Logger log = LoggerFactory.getLogger(WSDLResourceProcessor.class);
	
	public static final String WSDL_EXTENSION = "wsdl";
	
	@Override
	public InputStream preProcess(InputStream origin, EntaxyResource resource) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void postProcess(EntaxyResource resource) {
		if (!resource.exists())
			return;
		if (!resource.getName().endsWith("." + WSDL_EXTENSION))
			return;
		log.info("WSDL PROCESSOR FOR: [{}]", resource.getURL());
		EntaxyResourceMetadata meta = resource.getMetadata(true);
		EntaxyResourceMetadata.MetadataSection wsdlSection = meta.getSection("wsdl");
		wsdlSection.getContent().remove("error");
		try {
			
			generateMetadata(resource, meta);
			
			generateResources(resource, meta);
			
		} catch (Exception e) {
			log.error("Error processing WSDL for [" + resource.getURL() + "]", e);
			JsonObject errorData = new JsonObject();
			errorData.addProperty("exception", e.getClass().getName());
			errorData.addProperty("message", e.getMessage());
			if (e.getCause() != null) {
				errorData.addProperty("causeException", e.getCause().getClass().getName());
				errorData.addProperty("causeMessage", e.getCause().getMessage());
			}
			wsdlSection.getContent().add("error", errorData);
		}
		meta.update();
	}

	protected String convertUriToPath(String uri) {
		
		String result = uri;
		
		int index = uri.indexOf(":");
		if (index >= 0)
			result = result.substring(index+1);
		while (result.startsWith("/"))
			result = result.substring(1);
		
		result = result.replace(':', '/');
		
		return result;
		
	}
	
	protected void generateMetadata(EntaxyResource resource, EntaxyResourceMetadata meta) throws BusException, WSDLException {

		EntaxyResourceMetadata.MetadataSection section = meta.getSection(WSDLResourceMetadata.SECTION_NAME);

		// TODO move protocol to constant
		String url = "entaxy-resource://" + resource.getURL().replace(':', '/');
		String resourcesRootLocation = resource.getURL().replace(':', '/') + "/";
		WSDLManagerImpl wsdlManager = new WSDLManagerImpl();
		Definition definition = wsdlManager.getDefinition(url);
		Map services = definition.getAllServices();
		JsonArray serviceNames = new JsonArray();
		for (Object serviceKey: services.keySet()) {
			QName serviceName = (QName) serviceKey;
			serviceNames.add(serviceName.toString());
			JsonObject serviceJson = new JsonObject();
			Service service = (Service)services.get(serviceKey);
			Map ports = service.getPorts();
			JsonArray portNames = new JsonArray();
			for (Object portKey: ports.keySet()) {
				String portName = portKey.toString();
				portNames.add(portName.toString());
				Port port = (Port)ports.get(portKey);
				JsonObject portJson = new JsonObject();
				List operations = port.getBinding().getBindingOperations();
				JsonArray operationNames = new JsonArray();
				for (Object operationObj: operations) {
					BindingOperation operation = (BindingOperation)operationObj;
					operationNames.add(operation.getName());
				}
				portJson.addProperty(WSDLResourceMetadata.PROPERTIES.SERVICE_PORT_NAME
						, portName);
				portJson.add(WSDLResourceMetadata.PROPERTIES.SERVICE_PORT_OPERATIONS
						, operationNames);
				serviceJson.add(port.getName(), portJson);
			}
			serviceJson.addProperty(WSDLResourceMetadata.PROPERTIES.SERVICE_NAME, serviceName.getLocalPart());
			serviceJson.addProperty(WSDLResourceMetadata.PROPERTIES.SERVICE_NAMESPACE, serviceName.getNamespaceURI());
			serviceJson.addProperty(WSDLResourceMetadata.PROPERTIES.SERVICE_RESOURCES_ROOT
					, resourcesRootLocation + convertUriToPath(serviceName.getNamespaceURI()) + '/' + serviceName.getLocalPart());
			serviceJson.add(WSDLResourceMetadata.PROPERTIES.SERVICE_PORTS, portNames);
			section.getContent().remove(service.getQName().toString());
			section.getContent().add(service.getQName().toString(), serviceJson);
		}
		section.getContent().remove(WSDLResourceMetadata.PROPERTIES.SERVICES);
		section.getContent().add(WSDLResourceMetadata.PROPERTIES.SERVICES, serviceNames);

		log.debug("Services: {}", definition.getAllServices());
		
	}

	protected void generateResources(EntaxyResource resource, EntaxyResourceMetadata meta) throws BusException, WSDLException {
		// '{"@LIFECYCLE":["general", "debug", "local"], "*": {"@SKIP": ["deploy+"]}}'
		String instructions = EntaxyProducerUtils.instructions()
				.lifecycle("general")
				.any().skip("deploy+")
				.getInstructionsString();
		
		MetadataSection section = meta.getSection(WSDLResourceMetadata.SECTION_NAME);
		if (section == null)
			return;
		
		JsonObject metaContent = section.getContent();
		if (!metaContent.has(WSDLResourceMetadata.PROPERTIES.SERVICES))
			return;
		JsonElement servicesE = metaContent.get(WSDLResourceMetadata.PROPERTIES.SERVICES);
		if (!servicesE.isJsonArray())
			return;

		JsonObject object = new JsonObject();
		object.addProperty(EntaxyObject.FIELDS.FACTORY_ID, "service-resources-soap");
		JsonObject properties = new JsonObject();

		JsonArray services = servicesE.getAsJsonArray();
		for (int i=0; i<services.size(); i++) {
			String serviceName = services.get(i).getAsString();
			if (!CommonUtils.isValid(serviceName)) 
				continue;
			if (!metaContent.has(serviceName))
				continue;
			
			JsonObject service = metaContent.get(serviceName).getAsJsonObject().deepCopy(); 
			
			JsonElement portsE = service.get(WSDLResourceMetadata.PROPERTIES.SERVICE_PORTS);
			if ((portsE == null) || !portsE.isJsonArray())
				continue;
			
			JsonArray ports = portsE.getAsJsonArray();
			
			for (int j=0; j<ports.size(); j++) {
				String portName = ports.get(j).getAsString();
				if (!CommonUtils.isValid(portName))
					continue;
				if (!service.has(portName))
					continue;

				service.remove("port");
				service.add("port", service.get(portName));
				
				properties.remove("service");
				properties.add("service", service);
				properties.addProperty("parentResource", resource.getURL());
				
				JsonObject currentObject = object.deepCopy();
				currentObject.add(EntaxyObject.FIELDS.PROPERTIES, properties);

				ServiceHelper.addExecution(new ProducerExecution(currentObject, instructions));
				
				
			}
			
			
		}
		
	}
	
	static class ProducerExecution implements ServiceHelper.DelayedExecution {

		JsonObject currentObject;
		String instructions;
		
		public ProducerExecution(JsonObject currentObject, String instructions) {
			this.currentObject = currentObject;
			this.instructions = instructions;
		}
		
		@Override
		public void execute() {
			
			log.debug("\n\t ****** RUNNING EXECUTION \n[{}]\ninstructions: {}", currentObject, instructions);
			
			ProducerResult result = ServiceHelper.getInstance().getEntaxyProducerService()
					.produce(currentObject, instructions);
				
				Map<String, List<Exception>> exceptions = result.getAllExceptions();
				if (!exceptions.isEmpty()) {
					log.error("ERRORS WHILE PROCESSING WSDL [generateResources]:");
					for (String key: exceptions.keySet()) {
						for (Exception e: exceptions.get(key))
							log.error("ERROR in command [" + key + "]", e);
					}
				}
		}
		
	}
	
}
