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

import be.iminds.ilabt.jfed.highlevel.controller.JFedHighLevelException;
import be.iminds.ilabt.jfed.highlevel.controller.Task;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.model.*;
import be.iminds.ilabt.jfed.highlevel.util.AggregateManagerWrapperFactory;
import be.iminds.ilabt.jfed.highlevel.util.SliceRegistryUtil;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 6/17/2015
 * Time: 11:50
 */
public final class ProvisionSliverTask extends CreateTask {
    private static final Logger LOG = LoggerFactory.getLogger(ProvisionSliverTask.class);
    //input parameters
    private final Slice slice;
    private final Server auth;
    private final Instant expirationTime;
    private final List<UserSpec> userSpecs;

    //results
    private String manifestRspec;

    private Set<Sliver> provisionedSlivers = null;

    private final AggregateManagerWrapperFactory aggregateManagerWrapperFactory;
    private final SfaModel sfaModel;
    private final HighLevelTaskFactory highLevelTaskFactory;
    private final SliceRegistryUtil sliceRegistryUtil;


    /**
     * Use this constructor to create a sliver that will be used immediately!
     */
    public ProvisionSliverTask(Slice slice, Instant expirationTime, Server auth,
                               List<UserSpec> userSpecs, AggregateManagerWrapperFactory aggregateManagerWrapperFactory,
                               SfaModel sfaModel, HighLevelTaskFactory highLevelTaskFactory, SliceRegistryUtil sliceRegistryUtil) {
        super("Provision Sliver @ " + auth.getDefaultComponentManagerUrn());
        this.slice = slice;
        this.expirationTime = expirationTime;
        this.auth = auth;
        this.userSpecs = userSpecs;

        this.aggregateManagerWrapperFactory = aggregateManagerWrapperFactory;
        this.sfaModel = sfaModel;
        this.highLevelTaskFactory = highLevelTaskFactory;
        this.sliceRegistryUtil = sliceRegistryUtil;
    }

    @Override
    public void doTask(TaskExecution taskExecution) throws JFedException, InterruptedException {
        // Preparation: get a list of existing slivers on the given authority for the given slice.
        // This allows us to see which slivers were created during the Provision-call
        List<Sliver> existingSlivers = slice.findSlivers(auth);

        AggregateManagerWrapper amWrapper =
                aggregateManagerWrapperFactory.getAggregateManagerWrapper(taskExecution, auth);

        assert amWrapper.hasSeperateAllocateAndProvision() : "AM Wrapper does not support provisioning!";


        LOG.info("createSlivers @ {}  with user SSH keys: {}", auth.getDefaultComponentManagerUrn(), sfaModel.getUserKeys()); //missing here
        try {
            //don't set date here if CreateSliver doesn't set it itself
            Instant usedExpirationDate =
                    amWrapper.isExirationdateSetByCreateSliver()
                            || auth.hasFlag(Server.Flag.workaroundForceRenewAfterCreateSliver)
                            ? expirationTime : null;

            this.manifestRspec = amWrapper.provisionAndStart(
                    slice.getUrn(),
                    slice.getCredentials(),
                    userSpecs,
                    usedExpirationDate != null ? Date.from(usedExpirationDate) : null
            );

            if (this.manifestRspec == null)
                throw new JFedHighLevelException("Provision of the sliver failed.");


            provisionedSlivers = new HashSet<>();

            Collection<Sliver> manifestSlivers =
                    SfaModelAbstractListener.findSliversInManifest(sfaModel, auth, slice.getUrn(), new ManifestRspecSource(manifestRspec, ModelRspecType.FX));
            if (manifestSlivers != null)
                provisionedSlivers.addAll(manifestSlivers);

            //add slivers found by sfaModel due to SliverStatus and Status calls.
            // AL2S is one case where this is needed, see FEDIBBTDEV-1392
            provisionedSlivers.addAll(slice.getSlivers().stream().filter(auth::equals).collect(Collectors.toSet()));

            //we don't want to process slivers that already existed!
            provisionedSlivers.removeAll(existingSlivers);

            if (!provisionedSlivers.isEmpty()) {
                sliceRegistryUtil.registerSliversAtSATaskExecution(provisionedSlivers, this);
                LOG.info("Found new slivers during the Provision-call!");

            } else
                LOG.debug("Could not find any slivers that were newly created in Provision call. This is OK, as they are typically created during the Allocate-call.");


        } catch (AggregateManagerWrapper.NotEnoughFreeResourcesException e) {
            failedDueToNotEnoughFreeResources = true;
            this.manifestRspec = null;
            throw new JFedHighLevelException("Creation of the sliver failed: could not map to resources " +
                    "(resource shortage or unsupported topology)", e);
        }
    }

    @Override
    public List<Task> initDependsOn() {
        return Collections.singletonList(highLevelTaskFactory.getSliceCredential(slice));
    }

    public Slice getSlice() {
        return slice;
    }

    public Server getServer() {
        return auth;
    }

    @Override
    public Collection<Sliver> getCreatedSlivers() {
        return provisionedSlivers != null ? Collections.unmodifiableSet(provisionedSlivers) : null;
    }


}
