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

import lombok.extern.slf4j.Slf4j;
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.*;
import org.gcube.application.cms.plugins.requests.BaseExecutionRequest;
import org.gcube.application.cms.plugins.requests.BaseRequest;
import org.gcube.application.cms.plugins.requests.IndexDocumentRequest;
import org.gcube.application.cms.plugins.requests.MaterializationRequest;
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.IndexerPluginDescriptor;
import org.gcube.application.geoportal.common.model.plugins.MaterializerPluginDescriptor;
import org.gcube.application.geoportal.common.model.plugins.OperationDescriptor;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.HandlerDeclaration;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor;
import org.gcube.application.geoportal.common.utils.Files;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
public class SimpleLifeCycleManager extends AbstractLifeCycleManager implements LifecycleManager {

    public static final String PLUGIN_ID="DEFAULT-SINGLE-STEP";


    public SimpleLifeCycleManager() {
        DESCRIPTOR.setId(PLUGIN_ID);
    }



    private static class Steps {
        public static final OperationDescriptor PUBLISH = new OperationDescriptor("PUBLISH","Materialize & index project");

        static{
            PUBLISH.setAppliableToPhases(Collections.singletonList(LifecycleInformation.CommonPhases.DRAFT_PHASE));
        }
    }


    @Override
    public Configuration getCurrentConfiguration(BaseRequest req) throws ConfigurationException {
        Configuration toReturn = super.getCurrentConfiguration(req);
        toReturn.setIndexes(new ArrayList<>());

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

        // Info on Public index
        try {
            indexRequest.setCallParameters(getPublicIndexParams(req));
            Index publicIndex = indexerPlugin.getIndex(indexRequest);
            publicIndex.put("flag", "public");
            toReturn.getIndexes().add(publicIndex);
        }catch(ConfigurationException e){
            toReturn.addErrorMessage("Unable to gather information on public GIS Centroids Index : "+e.getMessage());
            log.error("Unable to gather information on public GIS Centroids Index",e);
        }

        return toReturn;
    }


    protected Document getPublicIndexParams(BaseRequest req){
        Document callParameters = new Document();
        callParameters.put("workspace",Files.fixFilename(req.getUseCaseDescriptor().getId()+req.getContext().getName()));
        callParameters.put("indexName",Files.fixFilename(req.getUseCaseDescriptor().getId()+req.getContext().getName()+"_centroids"));
        return callParameters;
    }


    protected Document getMaterializationParameters(BaseRequest request){
        Document params = new Document();
        String workspace = request.getUseCaseDescriptor().getId() + request.getContext().getId();
        params.put("workspace", Files.fixFilename(workspace));
        return params;
    }


    @Override
    protected void registerSteps() {
        setStep(new GuardedStepExecution(Steps.PUBLISH) {
            @Override
            protected StepExecutionReport run() throws Exception {
                //Check Performed by Guarded Step Execution
//                if(!theReport.getTheRequest().getDocument().getLifecycleInformation().getPhase().equals(LifecycleInformation.CommonPhases.DRAFT_PHASE))
//                    throw new StepException("Document is not in "+LifecycleInformation.CommonPhases.DRAFT_PHASE+" phase");

                // 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,getPublicIndexParams(theReport.getTheRequest()));
                    // setPhase
                    if(theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                        theReport.getToSetLifecycleInformation().setPhase("PUBLISHED");
                }
                return theReport;
            }
        });
    }

    protected void blockNonDraft(EventExecutionReport report) throws InvalidPluginRequestException {
        Boolean force = false;
        try {
            force = Boolean.parseBoolean(report.getTheRequest().getCallParameters().get("force").toString());
        }catch(Throwable t){}

        if(!report.getTheRequest().getDocument().getLifecycleInformation().getPhase().equals(LifecycleInformation.CommonPhases.DRAFT_PHASE) && ! force)
            throw new InvalidPluginRequestException("Document is not in "+LifecycleInformation.CommonPhases.DRAFT_PHASE+" phase");
    }

    @Override
    protected EventExecutionReport onInitDocument(EventExecutionReport report) throws InvalidPluginRequestException {
        blockNonDraft(report);
        return super.onInitDocument(report);
    }

    @Override
    protected EventExecutionReport onUpdateDocument(EventExecutionReport report) {
        return super.onUpdateDocument(report);
    }

    @Override
    protected EventExecutionReport onDeleteDocument(EventExecutionReport report) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {
        // Block non draft only if not force
        Boolean force=Boolean.parseBoolean(report.getTheRequest().getWithDefault("force","false"));
        log.debug("ON DELETE for {} : force is {}",report.getTheRequest().getDocument().getId(),force);
        if(!force) blockNonDraft(report);
        // dematerialize all
        JSONPathWrapper wrapper = new JSONPathWrapper(report.getTheRequest().getDocument().getTheDocument().toJson());
        for (String s : wrapper.getMatchingPaths("$..[?(@." + RegisteredFileSet.PAYLOADS + ")]")){
            log.info("Requesting dematerialization for {} ",s);
            for(MaterializationPlugin mat : getMaterializers(report.getTheRequest()))
                report = deMaterialize(report,mat,new Document("fileSetPath",s));
            if(!report.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                break;
        }
        if(report.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK)) {
            for(IndexerPluginInterface indexer : getIndexers(report.getTheRequest()))
                report = deIndex(report,indexer,getPublicIndexParams(report.getTheRequest()));
        }
        return report;
    }

    @Override
    protected EventExecutionReport onDeleteFileSet(EventExecutionReport theReport) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {
        // dematerialize selected
        blockNonDraft(theReport);
        for(MaterializationPlugin mat : getMaterializers(theReport.getTheRequest()))
            deMaterialize(theReport,mat,
                    theReport.getTheRequest().getCallParameters());
        // de index
        for(IndexerPluginInterface indexer : getIndexers(theReport.getTheRequest()))
            if(theReport.getToSetLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                deIndex(theReport,indexer,getPublicIndexParams(theReport.getTheRequest()));
        return theReport;
    }


    @Override
    protected void registerEvents() {
        super.registerEvents();
    }


    protected List<IndexerPluginInterface> getIndexers(BaseRequest request) throws ConfigurationException {
        log.trace("Looking for Indexers for {}",request);
        ArrayList<IndexerPluginInterface> toReturn=new ArrayList<>();
        UseCaseDescriptor desc = request.getUseCaseDescriptor();
        List<HandlerDeclaration> indexers = desc.getHandlersByType(IndexerPluginDescriptor.INDEXER);
        log.debug("Found UCD [{}] Indexers : {}",desc.getId(),indexers.size());
        for (HandlerDeclaration handlerDeclaration : indexers)
            toReturn.add((IndexerPluginInterface) pluginManager.getById(handlerDeclaration.getId()));
        return toReturn;
    }

    protected List<MaterializationPlugin> getMaterializers(BaseRequest request) throws ConfigurationException {
        log.trace("Looking for materializers for {}",request);
        ArrayList<MaterializationPlugin> toReturn=new ArrayList<>();
        UseCaseDescriptor desc = request.getUseCaseDescriptor();
        List<HandlerDeclaration> materializers = desc.getHandlersByType(MaterializerPluginDescriptor.MATERIALIZER);
        log.debug("Found UCD [{}] Materializers : {}",desc.getId(),materializers.size());
        for (HandlerDeclaration handlerDeclaration : materializers) {
            toReturn.add((MaterializationPlugin) pluginManager.getById(handlerDeclaration.getId()));
        }
        return toReturn;
    }

    protected <T extends DocumentHandlingReport> T deIndex(T report, IndexerPluginInterface indexer, Document parameters) throws InvalidPluginRequestException {
            BaseExecutionRequest request = report.getTheRequest();
            IndexDocumentRequest indexRequest = new IndexDocumentRequest(
                    request.getUseCaseDescriptor(),request.getCaller(), request.getContext(),request.getDocument());

            indexRequest.setCallParameters(parameters);
            IndexDocumentReport indexReport = indexer.deindex(indexRequest);

            return handleReport(indexReport,report);

    }

    protected <T extends DocumentHandlingReport> T deMaterialize(T report, MaterializationPlugin plugin, Document parameters) throws InvalidPluginRequestException, MaterializationException {
        BaseExecutionRequest request = report.getTheRequest();
        MaterializationRequest matReq =
                new MaterializationRequest(request.getUseCaseDescriptor(),request.getCaller(), request.getContext(), request.getDocument());

        Document params = new Document();
        String workspace = request.getUseCaseDescriptor().getId() + request.getContext().getId();
        params.put("workspace", Files.fixFilename(workspace));

        matReq.setCallParameters(params);
        MaterializationReport matRep = plugin.dematerialize(matReq);

        return handleReport(matRep,report);
    }

    protected <T extends DocumentHandlingReport> T index(T report, IndexerPluginInterface indexer, Document parameters) throws InvalidPluginRequestException {
        BaseExecutionRequest request = report.getTheRequest();
        IndexDocumentRequest indexRequest = new IndexDocumentRequest(
                request.getUseCaseDescriptor(),request.getCaller(), request.getContext(),request.getDocument());

        indexRequest.setCallParameters(parameters);
        IndexDocumentReport indexReport = indexer.index(indexRequest);

        return handleReport(indexReport,report);

    }

    protected <T extends DocumentHandlingReport> T materializeDocument(T report,MaterializationPlugin plugin,Document parameters) throws InvalidPluginRequestException, MaterializationException {
        BaseExecutionRequest request = report.getTheRequest();
        MaterializationRequest matReq =
                new MaterializationRequest(request.getUseCaseDescriptor(),request.getCaller(), request.getContext(), request.getDocument());

        matReq.setCallParameters(parameters);
        MaterializationReport matRep = plugin.materialize(matReq);

        return handleReport(matRep,report);
    }


    private <T extends DocumentHandlingReport> T handleReport(DocumentHandlingReport toHandle, T toUpdate){
        toUpdate.setResultingDocument(toHandle.getResultingDocument());
        LifecycleInformation info = toUpdate.getToSetLifecycleInformation();
        switch(toHandle.getStatus()){
            case OK : {
                info.setLastOperationStatus(LifecycleInformation.Status.OK);
                if(toHandle instanceof IndexDocumentReport)
                    toUpdate.setToSetIdentificationReferences(((IndexDocumentReport)toHandle).getToSetIdentificationReferences());
                // Propagate changes for further processings
                toUpdate.getTheRequest().getDocument().setTheDocument(toHandle.getResultingDocument());
                toUpdate.setToSetLifecycleInformation(toHandle.getToSetLifecycleInformation());

//                if(toHandle instanceof MaterializationReport)
//                    toUpdate.setToSetSpatialReference(((IndexDocumentReport)toHandle).getToSetSpatialReference());
                break;
            }
            case ERROR : {
                info.setLastOperationStatus(LifecycleInformation.Status.ERROR);
                toHandle.getMessages().forEach(s -> info.addErrorMessage(s));
                break;
            }
            case WARNING : {
                info.setLastOperationStatus(LifecycleInformation.Status.WARNING);
                toHandle.getMessages().forEach(s -> info.addWarningMessage(s));
                break;
            }
        }
        return toUpdate;
    }

}
