package org.gcube.application.cms.plugins.implementations;

import java.util.Collections;

import org.bson.Document;
import org.gcube.application.cms.plugins.IndexerPluginInterface;
import org.gcube.application.cms.plugins.LifecycleManager;
import org.gcube.application.cms.plugins.MaterializationPlugin;
import org.gcube.application.cms.plugins.faults.EventException;
import org.gcube.application.cms.plugins.faults.InvalidPluginRequestException;
import org.gcube.application.cms.plugins.faults.MaterializationException;
import org.gcube.application.cms.plugins.implementations.executions.GuardedStepExecution;
import org.gcube.application.cms.plugins.reports.EventExecutionReport;
import org.gcube.application.cms.plugins.reports.StepExecutionReport;
import org.gcube.application.cms.plugins.requests.BaseRequest;
import org.gcube.application.geoportal.common.model.JSONPathWrapper;
import org.gcube.application.geoportal.common.model.configuration.Configuration;
import org.gcube.application.geoportal.common.model.configuration.Index;
import org.gcube.application.geoportal.common.model.document.filesets.RegisteredFileSet;
import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation;
import org.gcube.application.geoportal.common.model.plugins.OperationDescriptor;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.utils.Files;

import com.vdurmont.semver4j.Semver;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Default3PhaseManager extends SimpleLifeCycleManager implements LifecycleManager {

	protected static class Phases {
        public static final String PENDING_APPROVAL="Pending Approval";
        public static final String PUBLISHED=SimpleLifeCycleManager.Phases.PUBLISHED;

    }

    public static final class STEPS{
        public static final OperationDescriptor SUBMIT=new OperationDescriptor("SUBMIT-FOR-REVIEW","Submits the Draft for reviewing");
        public static final OperationDescriptor REJECT=new OperationDescriptor("REJECT-DRAFT","Rejects the submitted Draft");
        public static final OperationDescriptor APPROVE=new OperationDescriptor("APPROVE-SUBMITTED","Approves the submitted Draft");
        public static final OperationDescriptor UNPUBLISH = new OperationDescriptor("UNPUBLISH","UnPublish the project. DeMaterialize and DeIndex the project");

        static {
            SUBMIT.setAppliableToPhases(Collections.singletonList(LifecycleInformation.CommonPhases.DRAFT_PHASE));
            REJECT.setAppliableToPhases(Collections.singletonList(Phases.PENDING_APPROVAL));
            APPROVE.setAppliableToPhases(Collections.singletonList(Phases.PENDING_APPROVAL));
            UNPUBLISH.setAppliableToPhases(Collections.singletonList(Phases.PUBLISHED));
        }
    }

    private static class PARAMETERS{
        public static final String NOTES="notes";
    }


    @Override
    protected EventExecutionReport onDeleteDocument(EventExecutionReport report) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {
        report =  super.onDeleteDocument(report);
        for(IndexerPluginInterface indexer : getIndexers(report.getTheRequest()))
            report= deIndex(report,indexer,getInternalIndexParams(report.getTheRequest()));
        return report;
    }

    @Override
    protected EventExecutionReport onDeleteFileSet(EventExecutionReport theReport) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {
        theReport =  super.onDeleteFileSet(theReport);
        String phase = theReport.getTheRequest().getDocument().getLifecycleInformation().getPhase();
        Document parameters = null;
        if(phase.equals(Phases.PENDING_APPROVAL))
            parameters =getInternalIndexParams(theReport.getTheRequest());
        if(phase.equals(Phases.PUBLISHED))
            parameters = getPublicIndexParams(theReport.getTheRequest());
        if(parameters!= null) {
        	//Fixed by Francesco, see #24902. Now is calling deIndex
            for(IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest())) {
                theReport = deIndex(theReport,indexer,getPublicIndexParams(theReport.getTheRequest()));
            }
        }
        return theReport;
    }

    @Override
    protected void registerSteps() {
        // register steps
        setStep(new GuardedStepExecution(STEPS.SUBMIT) {
            @Override
            protected StepExecutionReport run() throws Exception {
                return executeSubmit(theReport);
            }
        });
        setStep(new GuardedStepExecution(STEPS.APPROVE) {
            @Override
            protected StepExecutionReport run() throws Exception {
                return executeApprove(theReport);
            }
        });
        setStep(new GuardedStepExecution(STEPS.REJECT) {
            @Override
            protected StepExecutionReport run() throws Exception {
                return executeReject(theReport);
            }
        });
        
        //Updated by Francesco
        setStep(d3PhaseUnPublishStep());
    }

    public Default3PhaseManager() {
        DESCRIPTOR.setId("DEFAULT-3PHASE");
        DESCRIPTOR.setDescription("Default 3-phase lifecycle manager. This plugin supports a simple moderated publication lifecycle.");
        DESCRIPTOR.setVersion(new Semver("1.0.0"));
        DESCRIPTOR.setLabel("Default 3-Phase");
    }
    
    //Updated by Francesco. DeIndexing (only) from PublicIndex and InternalIndex (GIS-CENTROIDS plugin with 'public' and "internal" flag)
    protected GuardedStepExecution d3PhaseUnPublishStep() {
    	
    	return new GuardedStepExecution(STEPS.UNPUBLISH) {
            @Override
            protected StepExecutionReport run() throws Exception {
            	log.info( STEPS.UNPUBLISH+ " running...");
            	//DeMaterialize
            	JSONPathWrapper wrapper = new JSONPathWrapper(theReport.getTheRequest().getDocument().getTheDocument().toJson());
				for (String s : wrapper.getMatchingPaths("$..[?(@." + RegisteredFileSet.PAYLOADS + ")]")){
                     log.info("Requesting dematerialization for {} ",s);
                     for(MaterializationPlugin mat : getMaterializers(theReport.getTheRequest()))
                    	 theReport = deMaterialize(theReport,mat,new Document("fileSetPath",s));
                     if(!theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                         break;
                 }
				//DeIndex
                 if(theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK)) {
                     for(IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest())) {
                    	 log.info("Requesting deindexing for {} ",indexer.getDescriptor());
                    	 theReport = deIndex(theReport,indexer,getPublicIndexParams(theReport.getTheRequest()));
                    	 theReport = deIndex(theReport,indexer,getInternalIndexParams(theReport.getTheRequest()));
                     }
                 }
                 
                 if(theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                 	theReport.getToSetLifecycleInformation().setPhase(LifecycleInformation.CommonPhases.DRAFT_PHASE);

                 
                return theReport;
            }
        };
    }


    @Override
    public Configuration getCurrentConfiguration(BaseRequest req) throws ConfigurationException {
        Configuration toReturn = super.getCurrentConfiguration(req);

        IndexerPluginInterface indexerPlugin;
        indexerPlugin = (IndexerPluginInterface) pluginManager.getById("SDI-Indexer-Plugin");
        BaseRequest indexRequest = new BaseRequest(req.getUseCaseDescriptor(),req.getCaller(),req.getContext());

        // Info on internal_index
        try {
            indexRequest.setCallParameters(getInternalIndexParams(req));
            Index internalIndex = indexerPlugin.getIndex(indexRequest);
            internalIndex.put("flag", "internal");
            toReturn.getIndexes().add(internalIndex);
        }catch(ConfigurationException e){
            toReturn.addErrorMessage("Unable to gather information on internal GIS Centroids Index : "+e.getMessage());
            log.error("Unable to gather information on internal GIS Centroids Index",e);
        }
        return toReturn;
    }
    


    protected Document getInternalIndexParams(BaseRequest req){
        Document callParameters = new Document();

        callParameters.put(IndexConstants.INDEX_PARAMETER_WORKSPACE, Files.fixFilename(req.getUseCaseDescriptor().getId()+"_internal_"+req.getContext().getName()));
        callParameters.put(IndexConstants.INDEX_PARAMETER_INDEXNAME,Files.fixFilename(req.getUseCaseDescriptor().getId()+"_internal_"+req.getContext().getName()+"_centroids"));
        //Added by Francesco
        callParameters.put(IndexConstants.INDEX_PARAMETER_FLAGINTERNALINDEX, Boolean.TRUE);
        return callParameters;
    }

	protected StepExecutionReport executeSubmit(StepExecutionReport theReport) throws Exception {
		log.info(STEPS.SUBMIT + " running...");

		// Materialize
		for (MaterializationPlugin mat : getMaterializers(theReport.getTheRequest()))
			theReport = materializeDocument(theReport, mat, getMaterializationParameters(theReport.getTheRequest()));
		if (theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK)) {
			// Index
			for (IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest()))
				theReport = index(theReport, indexer, getInternalIndexParams(theReport.getTheRequest()));
			// setPhase
			if (theReport.getToSetLifecycleInformation().getLastOperationStatus()
					.equals(LifecycleInformation.Status.OK))
				theReport.getToSetLifecycleInformation().setPhase(Phases.PENDING_APPROVAL);
		}
		return theReport;
	}

	protected StepExecutionReport executeApprove(StepExecutionReport theReport) throws Exception {
		log.info(STEPS.APPROVE + " running...");

		// Index
		for (IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest()))
			theReport = index(theReport, indexer, getPublicIndexParams(theReport.getTheRequest()));
		// setPhase
		if (theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
			theReport.getToSetLifecycleInformation().setPhase(Phases.PUBLISHED);
		return theReport;
	}

	//Updated by Francesco, see #24877
	protected StepExecutionReport executeReject(StepExecutionReport theReport) throws Exception {
		log.info(STEPS.REJECT + " running...");

		// DeMaterialize
		JSONPathWrapper wrapper = new JSONPathWrapper(theReport.getTheRequest().getDocument().getTheDocument().toJson());
		for (String s : wrapper.getMatchingPaths("$..[?(@." + RegisteredFileSet.PAYLOADS + ")]")) {
			log.info("Requesting dematerialization for {} ", s);
			for (MaterializationPlugin mat : getMaterializers(theReport.getTheRequest()))
				theReport = deMaterialize(theReport, mat, new Document("fileSetPath", s));
			if (!theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
				break;
		}
		// DeIndexing
		if (theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK)) {
			for (IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest())) {
				log.info("Requesting deindexing for {} ", indexer.getDescriptor());
				theReport = deIndex(theReport, indexer, getInternalIndexParams(theReport.getTheRequest()));
			}
		}

		if (theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
			theReport.getToSetLifecycleInformation().setPhase(LifecycleInformation.CommonPhases.DRAFT_PHASE);

		return theReport;
	}


}
