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

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
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.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.SfaModelAbstractListener;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.Sliver;
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.AbstractGeniAggregateManager;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.rspec.model.ModelRspec;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 6/17/2015
 * Time: 11:50
 */
public final class AllocateSliverTask extends CreateTask {
    private static final Logger LOG = LoggerFactory.getLogger(AllocateSliverTask.class);

    //input parameters
    private final Slice slice;

    @Nullable
    private final Instant startTime;
    private final Instant endTime;

    private final List<UserSpec> userSpecs;

    @Nullable
    private final RequestRspecSource overridingRequestRspecSource;

    //helper fields
    private final Server server;

    //results
    private String manifestRspec;
    private Set<Sliver> allocatedSlivers = null;
    private AbstractGeniAggregateManager.AggregateManagerReply rawReply;


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


    public AllocateSliverTask(Slice slice, Instant startTime, Instant endTime, List<UserSpec> userSpecs, Server server, @Nullable RequestRspecSource overridingRequestRspecSource,
                              AggregateManagerWrapperFactory aggregateManagerWrapperFactory,
                              SliceRegistryUtil sliceRegistryUtil, SfaModel sfaModel,
                              HighLevelTaskFactory highLevelTaskFactory) {
        super("Allocate Sliver @ " + server.getDefaultComponentManagerUrn());
        this.slice = slice;
        this.startTime = startTime;
        this.endTime = endTime;
        this.userSpecs = userSpecs;
        this.server = server;
        this.overridingRequestRspecSource = overridingRequestRspecSource;

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

    }


    @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 CreateSliver-call

        List<Sliver> existingSlivers = slice.findSlivers(server);

        FXModelRspec.RequestRspecSpecialCases rspecSpecialCase = FXModelRspec.RequestRspecSpecialCases.NONE;
        if ((server.isServerType("planetlab"))
                || server.getDefaultComponentManagerUrn().startsWith("urn:publicid:IDN+ple:ibbtple+authority+")) {
            rspecSpecialCase = FXModelRspec.RequestRspecSpecialCases.PLE;
        }

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

        assert amWrapper.hasSeperateAllocateAndProvision();

        RequestRspecSource requestRspecSource =
                overridingRequestRspecSource != null ?
                        overridingRequestRspecSource : slice.getRequestRspec();
        LOG.trace("HLC.CreateSliverTask request RSpec before modification: expires={}", requestRspecSource.getModelRspec() != null ? requestRspecSource.getModelRspec().getExpiresString() : null);
        requestRspecSource = updateExpires(server, requestRspecSource, endTime);
        LOG.debug("HLC.CreateSliverTask request RSpec after modification: expires={}", requestRspecSource.getModelRspec() != null ? requestRspecSource.getModelRspec().getExpiresString() : null);
        String rspec = requestRspecSource.getRspecXmlString(rspecSpecialCase);


        LOG.info("createSlivers @ {}  with user SSH keys: {}", server.getDefaultComponentManagerUrn(), sfaModel.getUserKeys()); //missing here
        try {
            if (startTime == null) {

                //don't set date here if CreateSliver doesn't set it itself
                Instant usedExpirationDate =
                        amWrapper.isExirationdateSetByCreateSliver()
                                || server.hasFlag(Server.Flag.workaroundForceRenewAfterCreateSliver)
                                ? endTime : null;

                AggregateManagerWrapper.AllocateReply allocateReply = amWrapper.allocate(
                        slice.getUrn(),
                        slice.getCredentials(),
                        rspec,
                        usedExpirationDate != null ? Date.from(usedExpirationDate) : null
                );
                rawReply = amWrapper.getLastReply();
                if (allocateReply != null)
                    manifestRspec = allocateReply.getManifest();
            } else {
                if (server.hasFlag(Server.Flag.featureReservationsGeni)) {
                    AggregateManagerWrapper.AllocateReply allocateReply = amWrapper.allocate(slice.getUrn(),
                            slice.getCredentials(),
                            slice.getRequestRspec().getRspecXmlString(),
                            Date.from(startTime),
                            Date.from(endTime)
                    );

                    rawReply = amWrapper.getLastReply();

                    if (allocateReply != null)
                        manifestRspec = allocateReply.getManifest();
                } else if (server.hasFlag(Server.Flag.featureReservationsNitos)) {

                    ModelRspec requestWithLease =
                            CreateSliverTask.createRequestWithNitosLease(slice.getRequestRspec().getRspecXmlString(), server,
                                    startTime, endTime);

                    RequestRspecSource requestWithLeaseSource = new RequestRspecSource(requestWithLease);

                    requestWithLeaseSource = updateExpires(server, requestWithLeaseSource, endTime);

                    AggregateManagerWrapper.AllocateReply allocateReply = amWrapper.allocate(slice.getUrn(),
                            slice.getCredentials(),
                            requestWithLeaseSource.getRspecXmlString(),
                            Date.from(startTime),
                            Date.from(endTime));

                    rawReply = amWrapper.getLastReply();

                    if (allocateReply != null)
                        manifestRspec = allocateReply.getManifest();

                    List<GeniUrn> failedResources = new ArrayList<>();

                    if (manifestRspec != null) {
                        failedResources.addAll(CreateSliverTask.checkForFailedNitosReservations(manifestRspec, server, requestWithLease));

                        //check if lease was successful, otherwise set manifestRspec to null to indicate a reservation failure
                        if (!failedResources.isEmpty()) {
                            manifestRspec = null;
                            throw new JFedHighLevelException("Could not make a reservation for node(s) " + failedResources.stream().map(GeniUrn::toString).collect(Collectors.joining(", ")));
                        } //else: success!
                    } else {
                        throw new JFedHighLevelException("Reservation call was unsuccessful");
                    }

                } else {
                    throw new JFedHighLevelException("Allocating a reservation is not supported on this authority!");
                }


            }

        } 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);
        }

        //Note: listener that handles createSliver should update the list of component managers, as it will have changed
        if (this.manifestRspec == null)
            throw new JFedHighLevelException("Allocate of the sliver failed.");

        //either something failed, or the sliver is now being prepared. Either way, we will check the status

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

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

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

        if (!allocatedSlivers.isEmpty())
            sliceRegistryUtil.registerSliversAtSATaskExecution(allocatedSlivers, this);
        else
            LOG.info("Could not find any slivers that were newly created during Allocate call! This means that all new slivers MUST be created in Provision-call. " +
                    "Pre-existing slivers: " +
                    (existingSlivers.isEmpty() ?
                            "none"
                            : existingSlivers.stream()
                                    .map(Sliver::getUrnString)
                                    .collect(Collectors.joining(", "))));

    }

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

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

    public Slice getSlice() {
        return slice;
    }

    public Server getServer() {
        return server;
    }

    public String getManifestRspec() {
        return manifestRspec;
    }

    public AbstractGeniAggregateManager.AggregateManagerReply getRawReply() {
        return rawReply;
    }


}
