package be.iminds.ilabt.jfed.highlevel.jobs;

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.ExperimentPart;
import be.iminds.ilabt.jfed.experiment.ExperimentState;
import be.iminds.ilabt.jfed.experiment.SfaExperimentPart;
import be.iminds.ilabt.jfed.experiment.tasks.ExperimentTaskStatus;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.jobs.parts.ExperimentPartController;
import be.iminds.ilabt.jfed.highlevel.jobs.parts.ExperimentPartControllerManager;
import be.iminds.ilabt.jfed.highlevel.jobs.states.JobStateFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.AggregatesForSliceTask;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.TargetAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.time.Instant;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * User: twalcari
 * Date: 11/23/2015
 * Time: 16:47
 */
public class RestoreExperimentJob extends AbstractJob<Void> {
    private static final Logger LOG = LoggerFactory.getLogger(RestoreExperimentJob.class);


    private final JFedPreferences jFedPreferences;
    private final AuthorityFinder authorityFinder;
    private final JobStateFactory jobStateFactory;
    private final ExperimentPartControllerManager epcManager;

    public RestoreExperimentJob(Experiment experiment, JFedPreferences jFedPreferences, HighLevelTaskFactory hltf, TaskThread tt,
                                AuthorityFinder authorityFinder, JobStateFactory jobStateFactory,
                                ExperimentPartControllerManager epcManager) {
        super("Restore Experiment", experiment, hltf, tt);
        this.jFedPreferences = jFedPreferences;
        this.authorityFinder = authorityFinder;
        this.jobStateFactory = jobStateFactory;
        this.epcManager = epcManager;
    }

    @Override
    public Void execute() throws Exception {
        //1. Check if we have sufficient information to start the restoration

        if (!hasSufficientSliceInformation()) {
            //first fetch the necessary info

            FetchSliceInfoState fetchSliceInfoState = new FetchSliceInfoState();
            setAndRunState(fetchSliceInfoState);

            assert fetchSliceInfoState.getTaskExecution().isCompleted();
            if (fetchSliceInfoState.getTaskExecution().getState() == TaskExecution.TaskState.SUCCESS) {
                assert (experiment.getSlice().getExpirationDate() != null);

                if (experiment.getSlice().getExpirationDate().isBefore(Instant.now())) {
                    experiment.setExperimentState(ExperimentState.EXPIRED);

                    return null;
                }
            } else {
                //fetching slice info failed. We stop here!

                return null;
            }
        }

        assert hasSufficientSliceInformation();

        setAndRunState(new PopulateExperimentPartsState());

        //2. Now restore all parts of the experiment
        RestoreExperimentPartsSlicedState restoreExperimentPartsState = new RestoreExperimentPartsSlicedState();
        setAndRunState(restoreExperimentPartsState);

        experiment.setExperimentState(ExperimentState.CREATED);

        return null;
    }


    private boolean hasSufficientSliceInformation() {
        return (experiment.getSlice().getExpirationDate() != null);
    }


    private class FetchSliceInfoState extends SingleCallState<AggregatesForSliceTask> {
        public FetchSliceInfoState() {
            super("Requesting experiment information", hltf.getAggregatesForSlice(experiment.getSlice()));
        }
    }

    private class PopulateExperimentPartsState extends State {
        protected PopulateExperimentPartsState() {
            super("Processing experiment information");
        }

        @Nonnull
        @Override
        public ExperimentTaskStatus executeState(Job<?> job) throws InterruptedException, JFedException {
            if (experiment.getExistingRequestRspecSource() == null) {
                LOG.info("No (additional) slivers to restore as there is no existingRequestRspecSource for this experiment!");
                return ExperimentTaskStatus.WARNING;
            }

            if (!experiment.getSlice().getConnectAuthorities().isEmpty()) {
                LOG.debug("Retrieving SfaExperimentParts from "+experiment.getSlice().getConnectAuthorities().size()+" ConnectAuthorities");
                for (Server connectAuth : experiment.getSlice().getConnectAuthorities()) {
                    if (experiment.getPart(connectAuth) == null)
                        experiment.addExperimentPart(
                                new SfaExperimentPart(
                                        experiment,
                                        connectAuth/*connect auth*/,
                                        connectAuth/*component_manager auth -> same as connectAuth here, because we don't have more info*/)
                        );
                }
            } else {
                LOG.debug("Falling back to retrieving SfaExperimentParts from Request/Manifest Rspec");
                assert experiment.getExistingRequestRspecSource().getStringRspec().isWellFormed();

                Set<TargetAuthority> authorities = new HashSet<>();
                for (final GeniUrn componentManagerUrn : experiment.getExistingRequestRspecSource().getAllComponentManagerUrns()) {
                    String prefenceRecoverExoSM = jFedPreferences.getString(CorePreferenceKey.PREF_TESTBEDSPECIFIC_EXOGENI_RECOVER_METHOD).trim().toLowerCase();

                    final Server sfaAuthorityToConnect =
                            Objects.equals(prefenceRecoverExoSM, "single") ?
                                    authorityFinder.findByUrn(componentManagerUrn, AuthorityFinder.Purpose.RECOVER) :
                                    authorityFinder.findByUrn(componentManagerUrn, AuthorityFinder.Purpose.RECOVER_BOTH_FIRST);

                    final Server sfaAuthorityForRspec = authorityFinder.findByAnyUrn(componentManagerUrn, AuthorityFinder.Purpose.REQUEST_RSPEC);

                    if (sfaAuthorityToConnect == null) {
                        LOG.error("ERROR: Component Manager Urn in Request RSpec unknown (serverToConnect=null): \"" + componentManagerUrn + "\".");
                        throw new UnknownAuthorityException(componentManagerUrn);
                    }
                    if (sfaAuthorityForRspec == null) {
                        LOG.error("ERROR: Component Manager Urn in Request RSpec unknown (serverForRspecComponentManager=null): \"" + componentManagerUrn + "\".");
                        throw new UnknownAuthorityException(componentManagerUrn);
                    }

                    if (sfaAuthorityToConnect.isEdgeVlan())
                        continue;


                    authorities.add(new TargetAuthority(componentManagerUrn, sfaAuthorityForRspec, sfaAuthorityToConnect));


                    boolean isCentralBrokerTestbed =
                            sfaAuthorityToConnect.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth) ||
                            sfaAuthorityToConnect.hasFlag(Server.Flag.centralBrokerArchitectureSubauth);

                    if (isCentralBrokerTestbed) {
                        final Server secondSfaAuthorityToConnect =
                                authorityFinder.findByUrn(componentManagerUrn, AuthorityFinder.Purpose.RECOVER_BOTH_SECOND);

                        if (secondSfaAuthorityToConnect == null) {
                            LOG.error("ERROR: Component Manager Urn in Request RSpec unknown (secondSfaAuthorityToConnect=null): \"" + componentManagerUrn + "\".");
                            throw new UnknownAuthorityException(componentManagerUrn);
                        }

                        if (sfaAuthorityToConnect.isEdgeVlan())
                            continue;

                        authorities.add(new TargetAuthority(componentManagerUrn, sfaAuthorityForRspec, secondSfaAuthorityToConnect));
                    }
                }

                if (!authorities.isEmpty()) {
                    for (TargetAuthority auth : authorities) {
                        if (experiment.getPart(auth.getServerToConnect()) == null)
                            experiment.addExperimentPart(new SfaExperimentPart(experiment,
                                    auth.getServerToConnect()/*connect auth*/,
                                    auth.getServerForRspecComponentManager()/*component_manager auth*/));
                        else {
                            LOG.debug("An ExperimentPart already exists for authority {}", auth);
                        }
                    }
                } else {
                    LOG.debug("No additional authorities to contact");
                }
            }

            return ExperimentTaskStatus.SUCCESS;
        }
    }

    private class RestoreExperimentPartsSlicedState extends ExperimentPartsSlicedState<ExperimentPartStateSlice> {

        protected RestoreExperimentPartsSlicedState() {
            super(String.format("Requesting experiment information about %s from authorities ", experiment.getName()), experiment, epcManager);
        }

        @Override
        public <EP extends ExperimentPart> ExperimentPartStateSlice createJobSlice(ExperimentPartController<EP> controller, EP experimentPart) {
            return controller.restore(RestoreExperimentJob.this, experimentPart);
        }
    }
}
