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

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.SfaCredential;
import be.iminds.ilabt.jfed.lowlevel.api.PlanetlabSfaRegistryInterface;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.application.Platform;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.List;
import java.util.regex.Pattern;

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

    private final ParameterHistoryModel parameterHistoryModel;

    public PlanetlabSfaRegistryInterfaceListener(SfaModel sfaModel, ParameterHistoryModel parameterHistoryModel) {
        super(sfaModel);
        this.parameterHistoryModel = parameterHistoryModel;
    }

    private void seeUserCredential(AnyCredential credential) {
        if (credential instanceof SfaCredential) {
            SfaCredential c = (SfaCredential) credential;
            if (c.getTargetUrn() != null) {
                GeniUrn u = GeniUrn.parse(c.getTargetUrn());
                if (u != null && Objects.equals(u.getEncodedResourceType(), "user"))
                    seeUserUrn(u);
            }
            if (c.getOwnerUrn() != null) {
                GeniUrn u = GeniUrn.parse(c.getOwnerUrn());
                if (u != null)
                    seeUserUrn(u);
            }
        }
    }

    private void seeSliceCredential(@Nonnull Server auth, @Nonnull AnyCredential credential) {
        if (credential instanceof SfaCredential) {
            SfaCredential c = (SfaCredential) credential;
            String sliceUrnStr = c.getTargetUrn();
            assert sliceUrnStr != null;

            try {
                Slice slice = getModel().logExistSliceUrn(auth, sliceUrnStr);

                slice.setCredentials(Collections.singletonList(credential));
            } catch (GeniUrn.GeniUrnParseException e) {
                LOG.error("Got an invalid slice-urn {} in credential", sliceUrnStr, e);
            } catch (JFedHighLevelException e) {
                LOG.error("Could not register slice credential {}", credential, e);
            }
        }
    }

    private void seeUserUrn(GeniUrn urn) {
        //on sfawrap authorities, the user can not allways create slices on his own authority!
//        String subAuthName = PlanetlabSfaRegistryInterface.urnToSfaHrn(urn);
//        if (subAuthName != null)
//            getModel().addSubAuthorityName(subAuthName);
    }

    private void onGetCredentialResult(ApiCallDetails result) {
        LOG.trace("PlanetlabSfaRegistryInterfaceListener.onGetCredentialResult called.");
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //store credential
            AnyCredential anyCredential = (AnyCredential) result.getReply().getValue();
            String type = result.getMethodParameters().containsKey("type") ? null
                    : (String) result.getMethodParameters().get("type");

            //alternative method to find type
            if (anyCredential instanceof SfaCredential) {
                SfaCredential sc = (SfaCredential) anyCredential;
                GeniUrn target = GeniUrn.parse(sc.getTargetUrn());
                if (target != null && type == null)
                    type = target.getEncodedResourceType();
            }

            boolean knownType = false;
            if (type != null && (type.equals("user") || type.equals("authority"))) {
                parameterHistoryModel.addUserCredential(anyCredential);
                knownType = true;
                seeUserCredential(anyCredential);
            }
            if (Objects.equals(type, "slice")) {
                parameterHistoryModel.addSliceCredential(anyCredential);
                knownType = true;
                seeSliceCredential(result.getAuthority(), anyCredential);
            }
            if (!knownType) {
                LOG.warn("Unknown credential type passed to PlanetlabSfaRegistryInterfaceListener.onGetCredentialResult: \"" + type + "\". Adding as both slice and user credential.");
                parameterHistoryModel.addUserCredential(anyCredential);
                parameterHistoryModel.addSliceCredential(anyCredential);
            }
        }
    }

    private void onGetSelfCredentialResult(ApiCallDetails result) {
        LOG.trace("PlanetlabSfaRegistryInterfaceListener.onGetSelfCredentialResult called.");
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //store user credential
            AnyCredential userCredential = (AnyCredential) result.getReply().getValue();
            LOG.trace("PlanetlabSfaRegistryInterfaceListener.onGetSelfCredentialResult sets user credential.");
            getModel().setUserCredentials(Collections.singletonList(userCredential));
            seeUserCredential(userCredential);
        }
    }

    private void onRegister(ApiCallDetails result) {
        LOG.trace("PlanetlabSfaRegistryInterfaceListener.onRegister called.");

        String type = (String) ((Map) ((List) result.getXmlRpcRequest()).get(0)).get("type");
        assert type != null;
        if (!Objects.equals(type, "slice"))
            return;

        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //store slice info
            String hrn = (String) result.getMethodParameters().get("hrn");
            assert hrn != null :
                    "result was not called with hrn parameter. javaMethod=" + result.getJavaMethodName() + " call=" + result.getGeniMethodName();

            GeniUrn sliceUrn = PlanetlabSfaRegistryInterface.sfaHrnToUrn(hrn, "slice");

            LOG.debug("PlanetlabSfaRegistryInterfaceListener.onRegister registers slice " + sliceUrn);
            getModel().logExistSliceUrn(result.getAuthority(), sliceUrn);
        }
    }


    private void onResolve(ApiCallDetails result) {
        LOG.trace("PlanetlabSfaRegistryInterfaceListener.onRegister called.");

        String xrn = (String) result.getMethodParameters().get("xrn");
        assert xrn != null;

        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //store slice info

            Map struct = (Map) result.getReply().getValue();


            Object classtypeO = struct.get("classtype");
            if (classtypeO != null && classtypeO instanceof String && Objects.equals(classtypeO, "user")) {
                //"reg-urn": "urn:publicid:IDN+fed4fire:iminds+user+bvermeul",
                //"hrn": "fed4fire.iminds.bvermeul",

                Object regPiAuths = struct.get("reg-pi-authorities");
                if (regPiAuths != null && regPiAuths instanceof List) {
                    List<String> regPiAuthsList = (List<String>) regPiAuths;
                    LOG.debug("PlanetlabSfaRegistryInterface getSubAuthorityNames Resolve processing " + regPiAuthsList);
                    for (String regPiAuthName : regPiAuthsList) {
                        String[] parts = regPiAuthName.split(Pattern.quote("."));
                        if (parts.length > 1) {
//                            String regPiAuthNameEnd = parts[1];
//                            for (int i = 2; i < parts.length; i++)
//                                regPiAuthNameEnd += ":" + parts[i];
//                            getModel().addSubAuthorityName(regPiAuthNameEnd);
                            getModel().addLoggedInUserSubAuthorityName(regPiAuthName);
                        } else {
                            //ignore root authority (parts.length == 1)
                            if (parts.length <= 0)
                                LOG.warn("PlanetlabSfaRegistryInterface getSubAuthorityNames Resolve reply error: parts.length=" + parts.length + " for regSliceHrn=" + regPiAuthName);
                        }
                    }
                } else
                    LOG.warn("PlanetlabSfaRegistryInterface getSubAuthorityNames Resolve reply error: no reg-pi-authorities or not List: " + regPiAuths);
            }
            if (classtypeO != null && classtypeO instanceof String && Objects.equals(classtypeO, "slice")) {
                // "reg-urn": "urn:publicid:IDN+fed4fire:iminds:test1+slice+bvermeul",
                //"hrn": "fed4fire.iminds.test1.bvermeul",
                //"reg-researchers": [
                //        "fed4fire.iminds.brecht_vermeulen"
                //        ],
                //"authority": "fed4fire.iminds.test1",
            }
            if (classtypeO != null && classtypeO instanceof String && Objects.equals(classtypeO, "authority")) {
                // "reg-urn": "urn:publicid:IDN+fed4fire:iminds+authority+sa",
                // "hrn": "fed4fire.iminds",
                //"reg-pis": [ ... ]
                // "authority": "fed4fire",
            }

            Object regSlicesO = struct.get("reg-slices");
            if (regSlicesO != null && regSlicesO instanceof List) {
                List<String> regSlicesList = (List<String>) regSlicesO;
                for (String regSliceHrn : regSlicesList) {
                    String[] parts = regSliceHrn.split(Pattern.quote("."));
                    if (parts.length >= 2) {
                        GeniUrn sliceUrn = PlanetlabSfaRegistryInterface.sfaHrnToUrn(regSliceHrn, "slice");
                        getModel().logExistSliceUrn(result.getAuthority(), sliceUrn);
                    } else
                        LOG.warn("PlanetlabSfaRegistryInterface Resolve reply error: parts.length=" + parts.length + " for regSliceHrn=" + regSliceHrn);
                }
            } else
                LOG.warn("PlanetlabSfaRegistryInterface Resolve reply error: no reg-slices or not List: " + regSlicesO);
        }
    }

    @Override
    public void onResult(final ApiCallDetails details) {
        assert Platform.isFxApplicationThread();
        onResultInJavaFXThread(details);
    }

    public void onResultInJavaFXThread(ApiCallDetails result) {
        LOG.trace("PlanetlabSfaRegistryInterfaceListener.onResultInJavaFXThread called. api=" + result.getApiName());
        //ignore errors here
        if (result.getReply() == null || result.getJavaMethodName() == null)
            return;

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

        try {
//            if (result.getJavaMethodName().equals("getVersion"))
//                onGetVersionResult(result);
            LOG.trace("PlanetlabSfaRegistryInterfaceListener.onResultInJavaFXThread called for " + result.getJavaMethodName());

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

            if (Objects.equals(result.getJavaMethodName(), "getSelfCredential")) //GetSelfCredential call
                onGetSelfCredentialResult(result);

            if (Objects.equals(result.getJavaMethodName(), "getSelfCredential_AutomaticArguments")) //GetSelfCredential call
                onGetSelfCredentialResult(result);

            if (Objects.equals(result.getJavaMethodName(), "register")) //Register call
                onRegister(result);
            if (Objects.equals(result.getJavaMethodName(), "registerSlice")) //Register call
                onRegister(result);

            if (Objects.equals(result.getJavaMethodName(), "resolve")) //Register call
                onResolve(result);

//            if (result.getJavaMethodName().equals("getCredential")) //GetCredential
//                onGetCredentialResult(result);

//            if (result.getJavaMethodName().equals("resolve"))
//                onResolveResult(result);

//            if (result.getJavaMethodName().equals("list"))
//                onListResult(result);
        } catch (Exception e) {
            LOG.warn("WARNING: Exception when processing PlanetlabSfaRegistryInterface reply for SfaModel. " +
                    "This will be ignored, but it is most likely a bug. message: " + e.getMessage(), e);
        }
    }
}
