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.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniAMResponseCode;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.ProtogeniSliceAuthority;
import be.iminds.ilabt.jfed.lowlevel.resourceid.ResourceId;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.RFC3339Util;
import javafx.application.Platform;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.util.*;

/**
 * SfaModelSliceAuthorityListener: this watches for SliceAuthority calls and fills in SfaModel using the info in them
 */
public class SfaModelSliceAuthorityListener extends SfaModelAbstractListener {
    private final static org.slf4j.Logger LOG = LoggerFactory.getLogger(SfaModelSliceAuthorityListener.class);

    private final TestbedInfoSource testbedInfoSource;

    public SfaModelSliceAuthorityListener(SfaModel sfaModel, TestbedInfoSource testbedInfoSource) {
        super(sfaModel);
        this.testbedInfoSource = testbedInfoSource;
    }

    private void onGetVersionResult(ApiCallDetails result) {
        assert result.getReply() != null;

        //not really anything to store
        // if (result.getReply().getGeniResponseCode().isSuccess()) {        }
    }

    private void onGetSliceCredentialResult(ApiCallDetails result) {
        ResourceId sliceId = noteSliceUrnInParameters(result);
        noteUserCredentialInParameters(result);

        assert result.getReply() != null;

        if (result.getReply().getGeniResponseCode().isSuccess()) {
            if (Objects.equals(sliceId.getType(), "urn")) {
                String sliceUrn = sliceId.getValue();
                Slice slice;
                try {
                    slice = getModel().getSlice(sliceUrn);
                } catch (GeniUrn.GeniUrnParseException e) {
                    LOG.error("Could not get slice because of invalid URN {}", sliceUrn, e);
                    return;
                }

                AnyCredential sliceCredential = (AnyCredential) result.getReply().getValue();
                if (sliceCredential != null) {
                    if (sliceCredential.getExpiresDate() != null && sliceCredential.getExpiresDate().before(new Date())) {
                        //slice already expired! If we don't know it's expiration date, we will inform the sfaModel that it doesn't exist.
                        if (slice.getExpirationDate() == null)
                            getModel().logNotExistSlice(slice.getUrn());
                    } else {
                        try {
                            slice.setCredentials(Collections.singletonList(sliceCredential));
                        } catch (JFedHighLevelException e) {
                            LOG.error("Error while registering slice credential '{}'", sliceCredential, e);
                        }
                        noteSliceExpirationInSliceCredential(slice, sliceCredential);
                    }
                } else
                    LOG.warn("SfaModelSliceAuthorityListener.onGetSliceCredentialResult -> sliceCredential in result is null");

//                model.fireSliceChanged(slice);
            }
        }
    }

    /* extract subAuth from credential:  (not really usefull, ResolveUser parsing is enough)
    private void seeCredential(AnyCredential anyCredential) {
        if (anyCredential instanceof SfaCredential)
            seeSfaCredential((SfaCredential) anyCredential);
    }
    private void seeSfaCredential(SfaCredential sfaCredential) {
        seeUrn(sfaCredential.getTargetUrn());
    }*/
    private void seeUrn(String urnStr) {
        GeniUrn urn = GeniUrn.parse(urnStr);
        if (urn != null)
            seeUrn(urn);
    }

    private void seeUrn(GeniUrn urn) {
        String subAuthName = urn.getEncodedSubAuthName();
        if (subAuthName != null)
            getModel().addAnySubAuthorityName(subAuthName);
    }


    private void onGetCredentialResult(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //store user credential
            AnyCredential userCredential = (AnyCredential) result.getReply().getValue();
            getModel().setUserCredentials(Collections.singletonList(userCredential));
        }
    }

    private void onResolveSliceResult(ApiCallDetails result) throws JFedException {
        GeniUrn sliceId = noteSliceUrnInParameters(result);
        noteUserCredentialInParameters(result);

        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            if (Objects.equals(sliceId.getType(), "urn")) {
                ProtogeniSliceAuthority.SliceInfo sliceInfo =
                        (ProtogeniSliceAuthority.SliceInfo) result.getReply().getValue();

                Slice slice = getModel().getSlice(sliceId);

                assert slice != null;

                if (!Objects.equals(sliceInfo.getSliceUrn(), sliceId))
                    throw new JFedException("URN in Resolve answer (\"" + sliceInfo.getSliceUrn() + "\") is NOT the same as urn of slice (\"" + sliceId + "\").");

                slice.uuidProperty().setValue(sliceInfo.getUuid());
                slice.creator_uuidProperty().setValue(sliceInfo.getCreatorUuid());
                slice.creator_urnProperty().setValue(sliceInfo.getCreatorUrn().getValue());
                slice.setGid(sliceInfo.getGid());

                //TODO resolve doesn't contain expires, so we cannot update expires. But it does contain a gid, which might contain a date
                for (GeniUrn cmUrn : sliceInfo.getComponentManagers()) {
                    Server auth =
                            testbedInfoSource.getByUrn(cmUrn,
                                    TestbedInfoSource.SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY,
                                    TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY);

                    if (auth == null) continue;

                    //log existence of sliver
                    //note that can we know anything for sure? we don't know if sliver is on AM with geni_single true...
                    //note: as CM such as exogeni might have sub authority slivers, so this is actually not always certain. We will assume it anyway.
                    getModel().logExistAtLeastOneSliver(sliceInfo.getSliceUrn(), auth, Sliver.Source.SLICE_AUTHORITY);

                }
                //don't log non-existence: we can't know this for sure. Not all testbeds register slivers. For example PLE doesn't

            }
        } else {
            if (Objects.equals(result.getReply().getGeniResponseCode(), GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED)) {
                //"No such slice registered here";
                getModel().logNotExistSlice(sliceId);
            }

        }
    }

    private void onResolveUserResult(ApiCallDetails result) {
        noteUserCredentialInParameters(result);

        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            assert result.getServer() != null;
            //store user info
            ProtogeniSliceAuthority.UserInfo userInfo =
                    (ProtogeniSliceAuthority.UserInfo) result.getReply().getValue();

            //log slice names
            for (GeniUrn sliceUrn : userInfo.getSlices()) {
                getModel().logExistSliceUrn(result.getServer(), sliceUrn);
            }

            //log sub authority names
            for (Map.Entry<GeniUrn, String> entry : userInfo.getSubAuthorities().entrySet()) {
                GeniUrn urn = entry.getKey();

                String subAuthName = urn.getEncodedSubAuthName();
                if (subAuthName != null)
                    getModel().addLoggedInUserSubAuthorityName(subAuthName);

                seeUrn(urn);
            }
        }
    }

    private void onBindToSliceResult(ApiCallDetails result) {
        noteSliceCredentialInParameters(result);

        assert result.getReply() != null;
        //currently, we don't store slice sharing info
        //if (result.getReply().getGeniResponseCode().isSuccess()) { }
    }

    private void onRegisterResult(ApiCallDetails result) {
        assert result.getReply() != null;
        noteUserCredentialInParameters(result);
        ResourceId sliceId = noteSliceUrnInParameters(result);
        //we could also check other failures, for example, when calling register when a slice already exists.
        //but it's not really needed

        if (result.getReply().getGeniResponseCode().isSuccess()) {
            if (Objects.equals(sliceId.getType(), "urn")) {
                String sliceUrn = sliceId.getValue();
                Slice slice;
                try {
                    slice = getModel().getSlice(sliceUrn);

                    AnyCredential sliceCredential = (AnyCredential) result.getReply().getValue();
                    try {
                        if (sliceCredential != null)
                            slice.setCredentials(Collections.singletonList(sliceCredential));
                    } catch (JFedHighLevelException e) {
                        LOG.error("Error while registering slice credential '{}'", sliceCredential, e);
                    }

                    noteSliceExpirationInSliceCredential(slice, sliceCredential);

                } catch (GeniUrn.GeniUrnParseException e) {
                    LOG.error("Could not process register result because of an invalid URN {}", sliceUrn, e);
                }
            }
        }
    }

    private void onRenewSliceResult(ApiCallDetails result) throws ParseException {
        ResourceId sliceId = noteSliceCredentialInParameters(result);

        assert result.getReply() != null;
        if (sliceId != null && result.getReply().getGeniResponseCode().isSuccess()) {
            String expiration_rfc3339 = (String) result.getMethodParameters().get("expiration_rfc3339");
            String sliceUrn = sliceId.getValue();
            Slice slice;
            try {
                slice = getModel().getSlice(sliceUrn);
                slice.setExpirationDate(RFC3339Util.rfc3339StringToDate(expiration_rfc3339).toInstant());

                if (result.getReply().getValue() != null) {
                    AnyCredential newSliceCredential = (AnyCredential) result.getReply().getValue();
                    noteSliceExpirationInSliceCredential(slice, newSliceCredential);

                    try {
                        slice.setCredentials(Collections.singletonList(newSliceCredential));
                    } catch (JFedHighLevelException e) {
                        LOG.error("Error while registering slice credentials '{}'", newSliceCredential, e);
                    }
                }
            } catch (GeniUrn.GeniUrnParseException e) {
                LOG.error("Could not process renew result because of an invalid URN {}", sliceUrn, e);
            }
        }
    }

    private void onShutdownResult(ApiCallDetails result) {
        ResourceId sliceId = noteSliceCredentialInParameters(result);

        assert result.getReply() != null;
        if (sliceId != null && result.getReply().getGeniResponseCode().isSuccess()) {
            //todo: shutdown is currently handled as if the slice does not exists
            assert sliceId instanceof GeniUrn;
            getModel().logNotExistSlice((GeniUrn) sliceId);
        }
    }

    private void onGetKeysResult(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            List keyStrings = (List) result.getReply().getValue();

            //List<String> keys = new ArrayList<String>();
            for (Object k : keyStrings)// keys.add((String) k);
                getModel().addUserKey((String) k);
        }
    }

    private void onRemoveResult(ApiCallDetails result) {
        ResourceId sliceId = noteSliceCredentialInParameters(result);

        assert result.getReply() != null;
        if (sliceId != null && result.getReply().getGeniResponseCode().isSuccess()) {
            //on success, noteSliceCredentialInParameters will have inferred the slice existed.
            // That WAS correct at the time of the call, but by the end of the call, it doesn't exist anymore...
            assert sliceId instanceof GeniUrn;
            getModel().logNotExistSlice((GeniUrn) sliceId);
        }
    }

    @Override
    public void onResult(final ApiCallDetails result) {
        //Since this triggers updates to the GUI (because it changes Properties) this has to be executed on the JavaFX thread.
        assert Platform.isFxApplicationThread();

        //ignore errors here
        if (result.getReply() == null || result.getJavaMethodName() == null)
            return;

        if (!Objects.equals(result.getApiName(), ProtogeniSliceAuthority.getApiName()))
            return;

        try {
            if (Objects.equals(result.getJavaMethodName(), "getVersion"))
                onGetVersionResult(result);

            if (Objects.equals(result.getJavaMethodName(), "getSliceCredential"))
                onGetSliceCredentialResult(result);

            if (Objects.equals(result.getJavaMethodName(), "getCredential"))
                onGetCredentialResult(result);

            if (Objects.equals(result.getJavaMethodName(), "resolveSlice"))
                onResolveSliceResult(result);

            if (Objects.equals(result.getJavaMethodName(), "resolveUser"))
                onResolveUserResult(result);

            if (Objects.equals(result.getJavaMethodName(), "bindToSlice"))
                onBindToSliceResult(result);

            if (Objects.equals(result.getJavaMethodName(), "register"))
                onRegisterResult(result);

            if (Objects.equals(result.getJavaMethodName(), "renewSlice"))
                onRenewSliceResult(result);

            if (Objects.equals(result.getJavaMethodName(), "shutdown"))
                onShutdownResult(result);

            if (Objects.equals(result.getJavaMethodName(), "getKeys"))
                onGetKeysResult(result);

            if (Objects.equals(result.getJavaMethodName(), "remove"))
                onRemoveResult(result);
        } catch (Exception e) {
            LOG.warn("Exception when processing SliceAuthority reply for SfaModel. This will be ignored, but it is most likely a bug. ", e);
        }
    }
}
