package org.gcube.application.geoportal.storage;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;

import org.gcube.application.geoportal.model.content.GeoServerContent;
import org.gcube.application.geoportal.model.content.PersistedContent;
import org.gcube.application.geoportal.model.content.WorkspaceContent;
import org.gcube.application.geoportal.model.fault.SDIInteractionException;
import org.gcube.application.geoportal.model.gis.BBOX;
import org.gcube.application.geoportal.model.gis.SDILayerDescriptor;
import org.gcube.application.geoportal.utils.Files;
import org.gcube.common.storagehub.client.dsl.FileContainer;
import org.gcube.data.transfer.library.DataTransferClient;
import org.gcube.data.transfer.library.TransferResult;
import org.gcube.data.transfer.library.faults.RemoteServiceException;
import org.gcube.data.transfer.model.Destination;
import org.gcube.data.transfer.model.DestinationClashPolicy;
import org.gcube.data.transfer.model.RemoteFileDescriptor;
import org.gcube.spatial.data.gis.GISInterface;
import org.gcube.spatial.data.gis.is.AbstractGeoServerDescriptor;

import it.geosolutions.geoserver.rest.GeoServerRESTPublisher;
import it.geosolutions.geoserver.rest.GeoServerRESTPublisher.UploadMethod;
import it.geosolutions.geoserver.rest.GeoServerRESTReader;
import it.geosolutions.geoserver.rest.decoder.RESTFeatureType;
import it.geosolutions.geoserver.rest.decoder.RESTLayer;
import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder;
import it.geosolutions.geoserver.rest.encoder.datastore.GSPostGISDatastoreEncoder;
import it.geosolutions.geoserver.rest.encoder.feature.GSFeatureTypeEncoder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;


@Slf4j
public class SDIManager {

	static private String DEFAULT_CRS="EPSG:4326";


	private GISInterface gis;
	@Getter
	private DataTransferClient dtGeoServer; 
	private String geoserverHostName;


	public SDIManager() throws SDIInteractionException {
		try{
			log.debug("Initializing GIS Interface..");
			gis=GISInterface.get();
			AbstractGeoServerDescriptor geoserver=gis.getCurrentGeoServer();						
			if(geoserver==null)	
				throw new Exception("Unable to contact data transfer for geoserver ");

			log.debug("Found geoserver descriptor "+geoserver);
			geoserverHostName=new URL(gis.getCurrentGeoServer().getUrl()).getHost();

			log.debug("Contacting Data Transfer from geoserver {} ",geoserverHostName);
			dtGeoServer=DataTransferClient.getInstanceByEndpoint("http://"+geoserverHostName);
			if(!gis.getCurrentGeoServer().getReader().existGeoserver())
				throw new Exception("Geoserver not reachable");
		}catch(Exception e) {
			throw new SDIInteractionException("Unable to initialize SDI Manager",e);
		}
	}

	
	public RemoteFileDescriptor getGeoServerRemoteFolder() throws RemoteServiceException {
		return dtGeoServer.getWebClient().getInfo("geoserver/GNA");
	}
	
	public String createWorkspace(String toCreate) throws SDIInteractionException {
		try {
			if(!gis.getCurrentGeoServer().getReader().getWorkspaceNames().contains(toCreate)) {
				log.debug("Creating workspace : "+toCreate);
				if(!gis.getCurrentGeoServer().getPublisher().createWorkspace(toCreate))
					throw new SDIInteractionException("Unable to create workspace "+toCreate);
			}else log.debug("Workspace "+toCreate+" exists.");
			return toCreate;
		} catch (IllegalArgumentException | MalformedURLException e) {
			throw new SDIInteractionException("Unable to create workspace "+toCreate,e);
		}
	}


	// GEOSERVER-PERSISTENCE-ID / GNA / PROJECT-ID/ FILENAME

	public GeoServerContent pushShapeLayerFileSet(SDILayerDescriptor currentElement,String workspace) throws SDIInteractionException{
		try {
//			String remoteFolder=null;
//			String fileName=null;

			log.debug("Publishing "+currentElement+" files to geoserver @ "+geoserverHostName);

			GeoServerContent content=new GeoServerContent();
			content.setGeoserverHostName(geoserverHostName);
			content.setWorkspace(workspace);

			WorkspaceManager wsManager=new WorkspaceManager(currentElement.getRecord());

			currentElement.getActualContent().forEach((PersistedContent c)->{
				try {
				if(c instanceof WorkspaceContent) {
					WorkspaceContent wc=(WorkspaceContent) c;
					FileContainer fc=wsManager.getFileById(wc.getStorageID());
						
					String filename=Files.fixFilename(fc.get().getName());
					
					Destination destination=new Destination(filename);
					destination.setCreateSubfolders(true);
					destination.setOnExistingFileName(DestinationClashPolicy.REWRITE);
					destination.setOnExistingSubFolder(DestinationClashPolicy.ADD_SUFFIX);
					destination.setPersistenceId("geoserver");
					destination.setSubFolder("GNA/"+currentElement.getRecord().getId()+"/"+filename);
					log.debug("Sending "+wc+" to "+destination);
					TransferResult result=dtGeoServer.httpSource(fc.getPublicLink(), destination);
					log.debug("Transferred "+result);
					content.getFileNames().add(filename);

					content.setGeoserverPath(result.getRemotePath().substring(0,result.getRemotePath().lastIndexOf("/")));
				}
				}catch(Throwable t) {
					log.warn("Unable to transfer Persisted content"+c,t);
				}
				
			});
			
			
			if(content.getFileNames().isEmpty())
				throw new SDIInteractionException("No Persisted content found in "+currentElement);
			
			String fileName=content.getFileNames().get(0);
			String remoteFolder=content.getGeoserverPath().substring(0,content.getGeoserverPath().lastIndexOf("/"));
			
			String toSetLayerName=fileName.substring(0,fileName.lastIndexOf("."));
			int count=0;
			while(gis.getCurrentGeoServer().getReader().getLayer(workspace,toSetLayerName)!=null){
				count++;
				toSetLayerName=fileName.substring(0,fileName.lastIndexOf("."))+"_"+count;
				log.debug("layer for "+fileName+" already existing, trying "+toSetLayerName);
			};
				
				
			String storeName=toSetLayerName+"_store";
			content.setStore(storeName);
			content.setFeatureType(toSetLayerName);

			GeoServerRESTPublisher publisher=gis.getCurrentGeoServer().getPublisher();
			log.debug("Trying to create remote workspace : "+workspace);
			createWorkspace(workspace);				
			log.debug("Publishing remote folder "+remoteFolder);			

			URL directoryPath=new URL("file:"+remoteFolder+"/"+toSetLayerName+".shp");


			//TODO Evaluate SRS 

			boolean published=publisher.publishShp(
					workspace, 
					storeName, 
					null, 
					toSetLayerName, 
					//					UploadMethod.FILE, // neeeds zip
					UploadMethod.EXTERNAL, // needs shp
					directoryPath.toURI(), 
					DEFAULT_CRS, //SRS
					""); // default style

			if(!published) {
				throw new SDIInteractionException("Unable to publish layer "+toSetLayerName+" under "+workspace+". Unknown Geoserver fault.");
			}

			currentElement.setLayerName(toSetLayerName);
			GeoServerRESTReader reader=gis.getCurrentGeoServer().getReader();
			RESTLayer l=reader.getLayer(workspace, toSetLayerName);
			RESTFeatureType f= reader.getFeatureType(l);
			/*http://geoserver1.dev.d4science.org/geoserver/gna_conc_18/wms?
			service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_18:pos&
			styles=&bbox=8.62091913167495,40.62975046683799,8.621178639172953,40.630257904721645&
			width=392&height=768&srs=EPSG:4326&format=application/openlayers */
			
			
			currentElement.setWmsLink(
					String.format("https://%1$s/geoserver/%2$s/wms?"
							+"service=WMS&version=1.1.0&request=GetMap&layers=%2$s:%3$s&"
							+ "styles=&bbox=%4$f,%5$f,%6$f,%7$f&srs=%8$s&format=application/openlayers&width=%9$d&height=%10$d",
									geoserverHostName,
									workspace,
									toSetLayerName,
									f.getMinX(),
									f.getMinY(),
									f.getMaxX(),
									f.getMaxY(),
									DEFAULT_CRS,
									400,
									400));
			
			currentElement.setWorkspace(workspace);
			currentElement.setBbox(new BBOX(f.getMaxY(), f.getMaxX(), f.getMinY(), f.getMinX()));
			
			
			// TODO Metadata
			return content;
//		} catch (InvalidSourceException | SourceNotSetException | FailedTransferException | InitializationException
//				| InvalidDestinationException | DestinationNotSetException e) {
//			throw new SDIInteractionException("Unable to transfer fileSet for content "+currentElement,e);
		} catch (SDIInteractionException e) {
			throw e;
		} catch (Throwable t) {
			throw new SDIInteractionException("Unexpected internal fault while interacting with SDI.",t);
		}
	}

	private String createStoreFromPostgisDB(String workspace,String storeName) throws SDIInteractionException  {
		//SET BY PROVISIONING
		GSPostGISDatastoreEncoder encoder=new GSPostGISDatastoreEncoder(storeName);
		encoder.setJndiReferenceName("java:comp/env/jdbc/postgres");
		encoder.setLooseBBox(true);
		encoder.setDatabaseType("postgis");
		encoder.setEnabled(true);
		encoder.setFetchSize(1000);
		encoder.setValidateConnections(true);
		try {
			log.debug("Looking for datastore "+storeName+" under "+workspace);

			if(gis.getCurrentGeoServer().getReader().getDatastore(workspace,storeName)==null)

				if(!gis.getCurrentGeoServer().getDataStoreManager().create(workspace, encoder))
					throw new SDIInteractionException("Unable to create store "+storeName+" in "+workspace);
			log.debug("Store "+storeName+" exists under "+workspace);
			return storeName;
		} catch (IllegalArgumentException | MalformedURLException e) {
			throw new SDIInteractionException("Unable to create store "+storeName,e);
		}

	}

	private String publishStyle(File sldFile,String name) throws SDIInteractionException {
		try {
			if(!gis.getCurrentGeoServer().getReader().existsStyle(name)) {
				log.debug("Registering style "+name);
				if(!gis.getCurrentGeoServer().getPublisher().publishStyle(sldFile, name))
					throw new SDIInteractionException("Unable to register style "+name);
			}else log.debug("Style "+name+" already existing");			
			return name;
		} catch (IllegalArgumentException | MalformedURLException e) {
			throw new SDIInteractionException("Unable to create style "+name,e);
		}

	}

	public String configureCentroidLayer(String name,String workspace,String storeName) throws SDIInteractionException {

		GSFeatureTypeEncoder fte=new GSFeatureTypeEncoder();
		fte.setAbstract("Centroid layer for "+name);
		fte.setEnabled(true);
		fte.setNativeCRS(DEFAULT_CRS);
		fte.setTitle(name);
		fte.setName(name);
		
		
		String style="clustered_centroids";

		GSLayerEncoder layerEncoder=new GSLayerEncoder();
		layerEncoder.setDefaultStyle(style);
		layerEncoder.setEnabled(true);
		layerEncoder.setQueryable(true);
		try {
			//Checking workspace 
			createWorkspace(workspace);
			//Checking store 
			createStoreFromPostgisDB(workspace, storeName);
			//Checkig layer
			publishStyle(Files.getFileFromResources("styles/clustered_points.sld"),style);
			if(gis.getCurrentGeoServer().getReader().getLayer(workspace, name)==null)
				if(!gis.getCurrentGeoServer().getPublisher().publishDBLayer(workspace, storeName, fte, layerEncoder))
					throw new SDIInteractionException("Unable to create layer "+name);
			log.debug("layer "+name+" already exists");
			return name;
		} catch (IllegalArgumentException | MalformedURLException e) {
			throw new SDIInteractionException("Unable to create layer "+name,e);
		}


	}


	public void deleteContent(GeoServerContent toDelete) throws IllegalArgumentException, MalformedURLException, RemoteServiceException {
		log.debug("Deleting geoserver layer "+toDelete);
		//delete layer
		GeoServerRESTPublisher publisher=gis.getCurrentGeoServer().getPublisher();		
		//delete store
		publisher.removeDatastore(toDelete.getWorkspace(), toDelete.getStore(), true);
		//delete WS if empty
		GeoServerRESTReader reader=gis.getCurrentGeoServer().getReader();
		if(reader.getDatastores(toDelete.getWorkspace()).isEmpty()) {
			log.debug("Deleting emtpy workspace "+toDelete.getWorkspace());
			publisher.removeWorkspace(toDelete.getWorkspace(), true);
		}		
		//delete file

		dtGeoServer.getWebClient().delete(toDelete.getGeoserverPath());
	}

}
