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

import be.iminds.ilabt.jfed.log.ApiCallDetails;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractFederationApi;
import be.iminds.ilabt.jfed.lowlevel.api.FederationMemberAuthorityApi1;
import be.iminds.ilabt.jfed.lowlevel.api.FederationMemberAuthorityApi2;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.KeyUtil;
import javafx.application.Platform;
import org.slf4j.LoggerFactory;

import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * SfaModelSliceAuthorityListener: this watches for SliceAuthority calls and fills in SfaModel using the info in them
 * <p/>
 * listens to both federation API 1 and 2!
 */
public class SfaUniformFederationMemberAuthorityApiListener extends SfaModelAbstractListener {
    private static final org.slf4j.Logger LOG =
            LoggerFactory.getLogger(SfaUniformFederationMemberAuthorityApiListener.class);

    private final GeniUserProvider geniUserProvider;

    public SfaUniformFederationMemberAuthorityApiListener(SfaModel sfaModel, GeniUserProvider geniUserProvider) {
        super(sfaModel);
        this.geniUserProvider = geniUserProvider;
    }

    private void onGetCredentials(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            GeniUrn memberUrn = (GeniUrn) result.getMethodParameters().get("memberUrn");
            assert memberUrn != null;

            assert result.getReply().getValue() instanceof List;
            @SuppressWarnings("unchecked")
            List<AnyCredential> userCredentials = (List<AnyCredential>) result.getReply().getValue();
            if (userCredentials != null && userCredentials.size() > 0) {
                if (geniUserProvider != null &&
                        geniUserProvider.isUserLoggedIn() &&
                        Objects.equals(memberUrn, geniUserProvider.getLoggedInGeniUser().getUserUrn()))
                    //store first in list //TODO support multiple
                    getModel().setUserCredentials(userCredentials);
                else {
                    LOG.warn("Credentials are not for user, adding them only to parameterHistoryModel.");
                    ParameterHistoryModel parameterHistoryModel = getModel().getParameterHistoryModel();
                    userCredentials.forEach(parameterHistoryModel::addUserCredential);
                }
                if (userCredentials.size() > 1)
                    LOG.warn("TODO: The Federation API MA returned multiple credentials, but this is not yet supported by jFed");
            }
        }
    }

    private void onLookupKeysApi1(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            Map<GeniUrn, List<AbstractFederationApi.MemberKeyInfo>> lookupResult =
                    (Map<GeniUrn, List<AbstractFederationApi.MemberKeyInfo>>) result.getReply().getValue();

            for (Map.Entry<GeniUrn, List<AbstractFederationApi.MemberKeyInfo>> entry : lookupResult.entrySet()) {
                List<AbstractFederationApi.MemberKeyInfo> keyInfoList = entry.getValue();

                for (AbstractFederationApi.MemberKeyInfo memberKeyInfo : keyInfoList) {
                    getModel().addUserKey(entry.getKey(), memberKeyInfo.getPublicKey());
                }
            }
        }
    }

    private void onLookupKeysApi2(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            AbstractFederationApi.MemberKeyInfoList lookupResult =
                    (AbstractFederationApi.MemberKeyInfoList) result.getReply().getValue();

            for (Map.Entry<GeniUrn, FederationMemberAuthorityApi2.MemberKeyInfo> entry : lookupResult.entrySet()) {
                FederationMemberAuthorityApi2.MemberKeyInfo keyInfo = entry.getValue();

                if (keyInfo.getPublicKey() != null && keyInfo.getKeyMember() != null) {
                    //log any seen user key
                    getModel().addUserKey(entry.getKey(), keyInfo.getPublicKey());
                }

            }
        }
    }

    private void onLookupMember2(ApiCallDetails result) {
        assert result.getReply() != null;
        if (result.getReply().getGeniResponseCode().isSuccess() &&
                result.getReply().getValue() != null &&
                result.getReply().getValue() instanceof AbstractFederationApi.MemberInfoList) {
            AbstractFederationApi.MemberInfoList lookupResult =
                    (AbstractFederationApi.MemberInfoList) result.getReply().getValue();
            if (lookupResult == null || lookupResult.size() != 1 || lookupResult.values().iterator().next().getX509pemCerts() == null)
                return;
            List<X509Certificate> certs = lookupResult.values().iterator().next().getX509pemCerts();
            for (X509Certificate cert : certs) {
                try {
                    //a bit of a hack, to find the user certificate in the chain
                    Collection<List<?>> altNames = cert.getSubjectAlternativeNames();
                    for (List<?> altName : altNames) {
                        Integer nameType = (Integer) altName.get(0);
                        String altKey = altName.get(1).toString();

                        if (altKey.contains("+user+")) {
                            GeniUrn memberUrn = GeniUrn.parse(altKey);
                            if (memberUrn != null)
                                getModel().addUserKey(memberUrn, KeyUtil.publicKeyToOpenSshAuthorizedKeysFormat(cert.getPublicKey()));
                            break;
                        }
                    }
                } catch (CertificateParsingException ignored) {
                    LOG.debug("Error parsing certificate received in lookup MEMBER \"_GENI_MEMBER_SSL_CERTIFICATE\" field. Will ignore certificate.");
                }
            }
        }

    }


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

    public void onResultInJavaFXThread(ApiCallDetails result) {
        //ignore errors here
        if (result.getReply() == null || result.getJavaMethodName() == null)
            return;

        if (!Objects.equals(result.getApiName(), FederationMemberAuthorityApi1.getApiName()) && !Objects.equals(result.getApiName(), FederationMemberAuthorityApi2.getApiName()))
            return;

        try {
            if (Objects.equals(result.getApiName(), FederationMemberAuthorityApi1.getApiName())) {
                if (Objects.equals(result.getJavaMethodName(), "lookupKeys"))
                    onLookupKeysApi1(result);

                if (Objects.equals(result.getJavaMethodName(), "getCredentials"))
                    onGetCredentials(result);
            }

            if (Objects.equals(result.getApiName(), FederationMemberAuthorityApi2.getApiName())) {
                if (result.getReply().getValue() instanceof AbstractFederationApi.MemberKeyInfoList)
                    onLookupKeysApi2(result);

                if (result.getReply().getValue() instanceof AbstractFederationApi.MemberInfoList)
                    onLookupMember2(result);

                if (Objects.equals(result.getJavaMethodName(), "getCredentials"))
                    onGetCredentials(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);
        }
    }
}
