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

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.gcube.application.cms.plugins.LifecycleManager;
import org.gcube.application.cms.plugins.PluginManagerInterface;
import org.gcube.application.cms.plugins.faults.*;
import org.gcube.application.cms.plugins.implementations.executions.GuardedEventManager;
import org.gcube.application.cms.plugins.implementations.executions.GuardedStepExecution;
import org.gcube.application.cms.plugins.reports.EventExecutionReport;
import org.gcube.application.cms.plugins.reports.InitializationReport;
import org.gcube.application.cms.plugins.reports.Report;
import org.gcube.application.cms.plugins.reports.StepExecutionReport;
import org.gcube.application.cms.plugins.requests.BaseRequest;
import org.gcube.application.cms.plugins.requests.EventExecutionRequest;
import org.gcube.application.cms.plugins.requests.StepExecutionRequest;
import org.gcube.application.geoportal.common.model.configuration.Configuration;
import org.gcube.application.geoportal.common.model.document.accounting.User;
import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation;
import org.gcube.application.geoportal.common.model.document.lifecycle.TriggeredEvents;
import org.gcube.application.geoportal.common.model.plugins.LifecycleManagerDescriptor;
import org.gcube.application.geoportal.common.model.plugins.OperationDescriptor;
import org.gcube.application.geoportal.common.model.plugins.PluginDescriptor;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.HandlerDeclaration;
import org.gcube.application.geoportal.common.utils.ContextUtils;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public abstract class AbstractLifeCycleManager extends AbstractPlugin implements LifecycleManager {

    protected static class Events{
        public static final OperationDescriptor INIT=new OperationDescriptor(EventExecutionRequest.Events.ON_INIT_DOCUMENT,"Sets defaults and validate");
        public static final OperationDescriptor UPDATE=new OperationDescriptor(EventExecutionRequest.Events.ON_UPDATE_DOCUMENT,"Sets defaults and validate");
        public static final OperationDescriptor DELETE=new OperationDescriptor(EventExecutionRequest.Events.ON_DELETE_DOCUMENT,"No op");
        public static final OperationDescriptor DELETE_FS=new OperationDescriptor(EventExecutionRequest.Events.ON_DELETE_FILESET,"No op");
    }

    @Setter
    protected PluginManagerInterface pluginManager;

    private Map<String, GuardedStepExecution> registeredSteps=new HashMap<>();
    private Map<String, GuardedEventManager> registeredEvent=new HashMap<>();


    protected void setEvent(GuardedEventManager m){
        OperationDescriptor op= m.getOp();
        DESCRIPTOR.getSupportedEvents().put(op.getId(),op);
        registeredEvent.put(op.getId(),m);
    }
    protected void setStep(GuardedStepExecution e){
        OperationDescriptor op= e.getOp();
        DESCRIPTOR.getSupportedSteps().put(op.getId(),op);
        registeredSteps.put(op.getId(),e);
    }

    public AbstractLifeCycleManager() {
        DESCRIPTOR.setSupportedSteps(new HashMap<>());
        DESCRIPTOR.setSupportedEvents(new HashMap<>());
        registerEvents();
        registerSteps();
    }

    protected EventExecutionReport onDeleteDocument(EventExecutionReport report) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {return report;}
    protected EventExecutionReport onDeleteFileSet(EventExecutionReport report) throws ConfigurationException, InvalidPluginRequestException, MaterializationException, EventException {return report;}
    protected EventExecutionReport onUpdateDocument(EventExecutionReport report){
        report = validate(report);
        report = setDefault(report);
        return report;
    }
    protected EventExecutionReport onInitDocument(EventExecutionReport report) throws InvalidPluginRequestException {
        report = validate(report);
        report = setDefault(report);
        return report;
    }



    protected void registerEvents(){
        setEvent(new GuardedEventManager(Events.INIT) {
            @Override
            protected EventExecutionReport run() throws Exception {
                return onInitDocument(theReport);
            }
        });
        setEvent(new GuardedEventManager(Events.UPDATE) {
            @Override
            protected EventExecutionReport run() throws Exception {
                return onUpdateDocument(theReport);
            }
        });
        setEvent(new GuardedEventManager(Events.DELETE) {
            @Override
            protected EventExecutionReport run() throws Exception {
                return onDeleteDocument(theReport);
            }
        });
        setEvent(new GuardedEventManager(Events.DELETE_FS) {
            @Override
            protected EventExecutionReport run() throws Exception {
                return onDeleteFileSet(theReport);
            }
        });
    }
    protected void registerSteps(){}


    protected LifecycleManagerDescriptor DESCRIPTOR=new LifecycleManagerDescriptor(";;;");


    @Override
    public StepExecutionReport performStep(StepExecutionRequest request) throws StepException, InvalidPluginRequestException, InvalidProfileException, ConfigurationException, InsufficientPrivileges {
        log.info("Serving Request {}",request);

        log.debug("Checking is STEP {} is supported by {}",request.getStep(),DESCRIPTOR.getId());
        if(!registeredSteps.containsKey(request.getStep()))
            throw new UnrecognizedStepException(("Invalid Step " + request.getStep()));

        HandlerDeclaration handlerDeclaration= getConfigurationFromProfile(request.getUseCaseDescriptor());
        log.debug("Checking user role {} against config {} ",request.getCaller(),handlerDeclaration);
        if(!canInvokeStep(request.getStep(),request.getCaller(), handlerDeclaration))
            throw new InsufficientPrivileges("User is not allowed to invoke "+request.getStep());

            StepExecutionReport report=new StepExecutionReport(request);
            LifecycleInformation info=report.getToSetLifecycleInformation();
            report.setStatus(Report.Status.OK);
            info.setLastOperationStatus(LifecycleInformation.Status.OK);
            info.setLastInvokedStep(request.getStep());

        try {
            GuardedStepExecution exec=registeredSteps.get(request.getStep());
            exec.setTheReport(report);
            exec.setHandlerConfiguration(handlerDeclaration);
            log.debug("Actually executing Step with {} ",exec);
            return exec.execute();
        }catch (StepException | InvalidPluginRequestException e){
            throw e;
        }catch (Throwable t) {
            log.error("Unable to perform step " + request.getStep(), t);
            String msg = "Unable to execute Step " + request.getStep() + ". Error was " + t.getMessage();
            report.setStatus(Report.Status.ERROR);
            report.putMessage(msg);
            info.setLastOperationStatus(LifecycleInformation.Status.ERROR);
            info.addErrorMessage(msg);
        }
        return report;
    }

    protected boolean canInvokeStep(String stepID, User u, HandlerDeclaration config) throws ConfigurationException {
        return new RoleManager(config).canInvokeStep(stepID,u);
    }


    @Override
    public InitializationReport initInContext() throws InitializationException {
        InitializationReport report = new InitializationReport();
        try{
            report.setStatus(Report.Status.OK);
        } catch (Throwable e) {
            log.error("Unable to initialize plugins in {} ", ContextUtils.getCurrentScope(),e);
            report.setStatus(Report.Status.WARNING);
            report.putMessage("Unable to initialize plugins in "+ ContextUtils.getCurrentScope()+" : "+e.getMessage());
        }
        return report;
    }

    @Override
    public InitializationReport init() throws InitializationException {
        InitializationReport report = new InitializationReport();
        try{
            report.setStatus(Report.Status.OK);

        } catch (Throwable e) {
            log.error("Unable to initialize plugins ",e);
            report.setStatus(Report.Status.WARNING);
            report.putMessage("Unable to initialize plugins : "+e.getMessage());
        }
        return report;
    }



   @Override
    public PluginDescriptor getDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public EventExecutionReport onEvent(EventExecutionRequest request) throws EventException, InvalidPluginRequestException {
        log.info("Executing Event {}",request);
        EventExecutionReport report=new EventExecutionReport(request);
        report.getToSetLifecycleInformation().addEventReport(new TriggeredEvents());
        TriggeredEvents info = report.getToSetLifecycleInformation().getLastEvent();
        info.setLastOperationStatus(LifecycleInformation.Status.OK);
        info.setEvent(request.getEvent());
        try {
            if(!registeredEvent.containsKey(request.getEvent()))
                throw new UnrecognizedEventException("Unexpected Event "+request.getEvent());

            return registeredEvent.get(request.getEvent()).setTheReport(report).execute();

        }catch (EventException e){
            throw e;
        }catch (Throwable t){
            log.error("Unable to execute on event "+request.getEvent(),t);
            String msg = "Unable to execute on event "+request.getEvent()+". Error was "+t.getMessage();
            info.setLastOperationStatus(LifecycleInformation.Status.ERROR);
            info.addErrorMessage(msg);
            report.setStatus(Report.Status.ERROR);
            report.putMessage(msg);
        }
        return report;
    }


    /**
     * Override this method for programmatic default values management
     *
     * @param currentReport
     * @return
     */
    public EventExecutionReport setDefault(EventExecutionReport currentReport){
        // Default implementation is no op
        return currentReport;
    }

    public EventExecutionReport validate(EventExecutionReport currentReport){
        // Default implementation is no op
        return currentReport;
    }

    @Override
    public void shutdown() throws ShutDownException {}

    @Override
    public Configuration getCurrentConfiguration(BaseRequest request) throws ConfigurationException {
        return new Configuration();
    }
}
