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

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.JFedHighLevelException;
import be.iminds.ilabt.jfed.log.ApiCallDetails;
import be.iminds.ilabt.jfed.log.ResultListener;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniAMResponseCode;
import be.iminds.ilabt.jfed.lowlevel.GeniResponseCode;
import be.iminds.ilabt.jfed.lowlevel.SfaCredential;
import be.iminds.ilabt.jfed.lowlevel.resourceid.ResourceId;
import be.iminds.ilabt.jfed.lowlevel.resourceid.ResourceUrn;
import be.iminds.ilabt.jfed.rspec.model.ManifestSliverFinder;
import be.iminds.ilabt.jfed.rspec.model.ModelRspec;
import be.iminds.ilabt.jfed.rspec.model.RspecLink;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.time.Instant;
import java.util.*;

/**
 * SfaModelAbstractListener
 */
public abstract class SfaModelAbstractListener implements ResultListener {
    private final static Logger LOG = LoggerFactory.getLogger(SfaModelAbstractListener.class);

    private final SfaModel sfaModel;

    public SfaModelAbstractListener(SfaModel sfaModel) {
        this.sfaModel = sfaModel;
    }

    protected SfaModel getModel() {
        return sfaModel;
    }

    /**
     * helper: GENIRESPONSE_SEARCHFAILED means not exist, success means exist. anything else is ignored.
     */
    protected void deriveSliceExistence(@Nonnull GeniUrn sliceId, @Nonnull GeniResponseCode geniResponseCode, @Nonnull Server authority) {
        if (Objects.equals(geniResponseCode, GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED))
            getModel().logNotExistSliverInSliceOnAuth(sliceId, authority);
        else if (geniResponseCode.isSuccess())
            getModel().logExistSlice(sliceId);
    }

    /**
     * helper that derives that a user credential is valid for the user: if it is used as argument and the call is
     * successful it is valid
     */
    protected void noteUserCredentialInParameters(ApiCallDetails result) {
        assert result.getReply() != null;

        if (result.getReply().getGeniResponseCode().isSuccess()) {
            if (result.getMethodParameters().containsKey("userCredential")) {
                AnyCredential userCredential = (AnyCredential) result.getMethodParameters().get("userCredential");
                if (userCredential != null) {
                    List<AnyCredential> credentialList = new ArrayList<>();
                    credentialList.add(userCredential);
                    getModel().setUserCredentials(credentialList);
                }
            }

            if (result.getMethodParameters().containsKey("credentialList")) {
                @SuppressWarnings("unchecked")
                List<AnyCredential> userCredentials =
                        (List<AnyCredential>) result.getMethodParameters().get("credentialList");
                if (userCredentials != null && userCredentials.size() > 0)
                    getModel().setUserCredentials(userCredentials);
            }
        }
    }

    /**
     * helper that derives that a slice credential is valid for the user: if it is used as argument and the call is
     * successful it is valid
     */
    protected ResourceId noteSliceCredentialInParameters(ApiCallDetails result) {
        assert result.getReply() != null;
        AnyCredential sliceCredential = (AnyCredential) result.getMethodParameters().get("sliceCredential");
        if (sliceCredential instanceof SfaCredential) {
            String sliceUrn = ((SfaCredential) sliceCredential).getTargetUrn();

            try {
                GeniUrn sliceId = new GeniUrn(sliceUrn);
                deriveSliceExistence(sliceId, result.getReply().getGeniResponseCode(), result.getServer());

                if (result.getReply().getGeniResponseCode().isSuccess()) {
                    Slice slice = getModel().getSlice(sliceId);

                    if (!slice.hasCredential()) {
                        try {
                            slice.setCredentials(Collections.singletonList(sliceCredential));
                        } catch (JFedHighLevelException e) {
                            LOG.error("Error while registering slice credential '{}'", sliceCredential, e);
                        }

                        noteSliceExpirationInSliceCredential(slice, sliceCredential);
                    }
                }

                return sliceId;
            } catch (GeniUrn.GeniUrnParseException e) {
                LOG.error("Could not process slice credential because of an invalid urn {}", sliceUrn, e);

                return new ResourceUrn(sliceUrn);
            }
        }

        return null;
    }

    protected void noteSliceExpirationInSliceCredential(Slice slice, AnyCredential sliceCredential) {
        //increase slice expiration time if slice expires later than before
        if (sliceCredential instanceof SfaCredential) {
            SfaCredential sfaSliceCredential = (SfaCredential) sliceCredential;
            Instant expiresDate = sfaSliceCredential.getExpiresDate().toInstant();
            if (expiresDate != null &&
                    (slice.getExpirationDate() == null || expiresDate.isAfter(slice.getExpirationDate())))
                slice.setExpirationDate(expiresDate);
        }
    }

    /**
     * helper
     */
    protected GeniUrn noteSliceUrnInParameters(ApiCallDetails result) {
        assert result.getReply() != null;
        GeniUrn id = null;
        if (result.getMethodParameters().get("slice") instanceof GeniUrn)
            id = (GeniUrn) result.getMethodParameters().get("slice");
        if (result.getMethodParameters().get("sliceUrn") instanceof GeniUrn)
            id = (GeniUrn) result.getMethodParameters().get("sliceUrn");
        deriveSliceExistence(id, result.getReply().getGeniResponseCode(), result.getServer());
        return id;
    }


    /**
     * Find and create slivers in manifest.
     *
     * @param auth the authority the manifest was received on. Note that if the manifest contains sliver_id for other authorities, they will be ignored! (sub authorities are ignored for this check)
     * @return the list of slivers, or null if no sliver info can be found in the manifest (sliver_id attribute). So this never returns an empty list!
     */
    public static List<Sliver> findSliversInManifest(SfaModel sfaModel, Server auth,
                                                     String sliceUrn, ManifestRspecSource manifestRspecSource)
            throws GeniUrn.GeniUrnParseException {
        return findSliversInManifest(sfaModel, auth, new GeniUrn(sliceUrn), manifestRspecSource);
    }

    /**
     * Find and create slivers in manifest.
     *
     * @param auth the authority the manifest was received on. Note that if the manifest contains sliver_id for other authorities, they will be ignored! (sub authorities are ignored for this check)
     * @return the list of slivers, or null if no sliver info can be found in the manifest (sliver_id attribute). So this never returns an empty list!
     */
    //TODO:check if this may return null
    public static List<Sliver> findSliversInManifest(SfaModel sfaModel, Server auth,
                                                     GeniUrn sliceUrn, ManifestRspecSource manifestRspecSource) {
        List<Sliver> sliverList = new ArrayList<>();
        Set<GeniUrn> seenSlivers = new HashSet<>();
        Set<GeniUrn> errorSlivers = new HashSet<>();

        //parse RSpec
//        ManifestRspecSource manifestRspecSource = new ManifestRspecSource(manifestRspecString, ModelRspecType.FX);
        ModelRspec rspec = manifestRspecSource.getModelRspec();
        if (rspec == null) return null;

        for (RspecNode node : rspec.getNodes()) {
            if (node.getSliverId() != null) {
                Sliver sliver =
                        sfaModel.logExistSliver(sliceUrn, node.getSliverId()/*sliver URN*/, auth, Sliver.Source.AGGREGATE_MANAGER_MANIFEST);
                if (sliver != null) {
                    if (node.getClientId() != null && node.getComponentId() != null)
                        sliver.logExistsComponent(node.getClientId(), node.getComponentId().toString());
                    else {
                        LOG.warn("findSliversInManifest: While processing sliver " + node.getSliverId() + ", found a node with client_id=" + node.getClientId() + " and component_id=" + node.getComponentId());
                    }
                    if (seenSlivers.add(sliver.getUrn()))
                        sliverList.add(sliver);
                } else {
                    LOG.warn("findSliversInManifest: Could not create/find sliver while processing node sliver " + node.getSliverId() + ". Something went wrong in sfaModel.logExistSliver. (possibly sliver does not match authority)");
                    errorSlivers.add(node.getSliverId());
                }
            }
        }

        for (RspecLink link : rspec.getLinks()) {
            if (link.getSliverId() != null) {
                Sliver sliver =
                        sfaModel.logExistSliver(sliceUrn, link.getSliverId()/*sliver URN*/, auth, Sliver.Source.AGGREGATE_MANAGER_MANIFEST);
                if (sliver != null) {
                    if (seenSlivers.add(sliver.getUrn()))
                        sliverList.add(sliver);
                } else {
                    LOG.warn("SfaModelAbstractListener.findSliversInManifest: Could not create/find sliver while processing link sliver " + link.getSliverId() + ". Something went wrong in sfaModel.logExistSliver. (possibly sliver does not match authority)");
                    errorSlivers.add(link.getSliverId());
                }
            }
//            //don't include interfaces as their status is never reported in the status-calls
//            for (RspecInterface iface : link.getInterfaces()) {
//                if(iface.getSliverId() != null) {
//                    Sliver sliver = sfaModel.logExistSliver(sliceUrn, iface.getSliverId().toString()/*sliver URN*/, auth);
//                    assert sliver != null;
//                    if (sliver != null && seenSlivers.add(sliver.getUrn()))
//                        sliverList.add(sliver);
//                }
//            }
        }

        Set<GeniUrn> unSeenSliverUrns = new HashSet<>(ManifestSliverFinder.findSliversInManifest(manifestRspecSource));
        unSeenSliverUrns.removeAll(seenSlivers);
        unSeenSliverUrns.removeAll(errorSlivers);

        for (GeniUrn sliverUrn : unSeenSliverUrns) {
            //non node or link sliver
            Sliver sliver =
                    sfaModel.logExistSliver(sliceUrn, sliverUrn, auth, Sliver.Source.AGGREGATE_MANAGER_MANIFEST);
            if (sliver == null) {
                LOG.warn("SfaModelAbstractListener.findSliversInManifest: " +
                        "Could not create/find sliver while processing sliver " + sliverUrn + ". " +
                        "Something went wrong in sfaModel.logExistSliver. (possibly sliver does not match authority)");
            } else
                sliverList.add(sliver);
        }

        if (sliverList.isEmpty())
            return null;
        return sliverList;
    }

    public List<Sliver> findSliversInManifest(Server auth, String sliceUrn, ManifestRspecSource manifestRspecSource) throws GeniUrn.GeniUrnParseException {
        return findSliversInManifest(getModel(), auth, sliceUrn, manifestRspecSource);
    }

    public List<Sliver> findSliversInManifest(Server auth, GeniUrn sliceUrn, ManifestRspecSource manifestRspecSource) {
        return findSliversInManifest(getModel(), auth, sliceUrn, manifestRspecSource);
    }
}
