package be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.PlanetlabSfaRegistryInterface;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.UserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.regex.Pattern;

/**
 * ProtoGeniSAUserAndSliceApiWrapper
 */
public class PlanetlabRegistryUserAndSliceApiWrapper extends UserAndSliceApiWrapper {
    private final static org.slf4j.Logger LOG = LoggerFactory.getLogger(PlanetlabRegistryUserAndSliceApiWrapper.class);
    List<AnyCredential> userCredentials;
    final Map<String, List<AnyCredential>> authCredentialsByHrn = new HashMap<>();
    private final Map<GeniUrn, Map> resolveReplyByUrn = new HashMap<>();

    public PlanetlabRegistryUserAndSliceApiWrapper(Logger logger, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
        super(logger, geniUserProvider, connectionProvider, jFedPreferences);
    }

    private SfaConnection getCon() throws JFedException {
        return getConnection(new ApiInfo.Api(ApiInfo.ApiName.PLANETLAB_SLICE_REGISTRY, 1));
    }

    private PlanetlabSfaRegistryInterface registry(Logger logger) {
        return new PlanetlabSfaRegistryInterface(logger, jFedPreferences);
    }

    @Override
    public synchronized List<AnyCredential> getUserCredentials(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
        userCredentials = null;

        List<AnyCredential> res = new ArrayList<>();

        PlanetlabSfaRegistryInterface.SimpleApiCallReply<AnyCredential> replyUserCred =
                registry(logger).getSelfCredential_AutomaticArguments(getCon(), getLoggedInUser());

        if (!replyUserCred.getGeniResponseCode().isSuccess())
            throw new JFedException("Could not retrieve user credential from server: code=" +
                    replyUserCred.getGeniResponseCode() + " output=" + replyUserCred.getOutput(),
                    replyUserCred
            );

        AnyCredential userCred = replyUserCred.getValue();
        res.add(userCred);
        userCredentials = new ArrayList<>(res);
        LOG.debug("stored planetlab credential at " + this);

        return res;
    }

    public synchronized List<AnyCredential> getAuthCredentials(Logger logger, GeniUrn userUrn, String authHrn) throws JFedException {
        userCredentials = null;
        List<AnyCredential> authCredentials = authCredentialsByHrn.computeIfAbsent(authHrn, k -> new ArrayList<>());
        authCredentials.clear();

        userCredentials = getUserCredentials(logger, userUrn);
        AnyCredential userCredential = userCredentials.get(0);

        PlanetlabSfaRegistryInterface.SimpleApiCallReply<AnyCredential> replyAuthorityCred =
                registry(logger).getCredential(getCon(), userCredential, authHrn, "authority");

        if (!replyAuthorityCred.getGeniResponseCode().isSuccess()) {
            LOG.error("Could not retrieve authority credential from server: code=" +
                            replyAuthorityCred.getGeniResponseCode() + " output=" + replyAuthorityCred.getOutput(),
                    replyAuthorityCred
            );
            return null;
        } else {
            authCredentials.add(replyAuthorityCred.getValue());
            return authCredentials;
        }
    }

    @Override
    public boolean hasUserCredentials() {
        return hasUserCredentials(userCredentials);
    }

    public List<AnyCredential> getCachedUserCredentialsForAM() {
        return userCredentials;
    }

    @Override
    public List<AnyCredential> getSliceCredentials(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn) throws JFedException {
        assert userCredentials != null;
        PlanetlabSfaRegistryInterface.SimpleApiCallReply<AnyCredential> replyCred =
                registry(logger).getCredential(getCon(), AnyCredential.toSingleCredential(userCredentials), sliceUrn.getValue(), "slice");

        if (!replyCred.getGeniResponseCode().isSuccess())
            throw new JFedException("Could not retrieve slice credential from server: code=" +
                    replyCred.getGeniResponseCode() + " output=" + replyCred.getOutput(),
                    replyCred
            );

        return Collections.singletonList(replyCred.getValue());
    }

    private Map getResolveReply(Logger logger, GeniUrn urn) throws JFedException {
        Map res = resolveReplyByUrn.get(urn);
        if (res == null) {
            //Resolve userHrn, userUrn or hrnStart ????
            //userUrn seems to work

            PlanetlabSfaRegistryInterface.SimpleApiCallReply<Map> reply =
                    registry(logger).resolve(getCon(), AnyCredential.toSingleCredential(userCredentials), urn.toString()/*xrn*/);
            if (reply.getGeniResponseCode().isSuccess()) {
                res = reply.getValue();
                resolveReplyByUrn.put(urn, res);
            }
        }
        return res;
    }


    @Override
    public List<GeniUrn> getSlicesForUser(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
        assert userCredentials != null;
        assert !userCredentials.isEmpty();

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

        Map struct = getResolveReply(logger, userUrn);
        Object regSlicesO = struct.get("reg-slices");
        if (regSlicesO != null && regSlicesO instanceof Object[]) {
            List<String> regSlicesList = AbstractApi.apiSpecifiesListOfString(regSlicesO);
            for (String regSliceHrn : regSlicesList) {
                String[] parts = regSliceHrn.split(Pattern.quote("."));
                if (parts.length >= 2) {
                    GeniUrn sliceUrn = PlanetlabSfaRegistryInterface.sfaHrnToUrn(regSliceHrn, "slice");
                    res.add(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);

        return res;
    }

    @Override
    public List<GeniUrn> getAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn) throws JFedException {
        //planetlab SfaRegistryInterface does not support this
        return new ArrayList<>();

//        throw new RuntimeException("Unimplemented: getAggregatesForSlice for Planetlab Registry");
    }

    @Override
    public Date getSliceExpiration(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn) throws JFedException {
        //not implemented: null will cause fall back to using credential in AutomaticUserAndSliceApiWrapper
        return null;
    }

    @Override
    public void shareSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn userUrn) throws JFedException {
        throw new RuntimeException("not implemented");
    }

    @Override
    public SliceInfo createSlice(@Nonnull Logger logger, @Nonnull String sliceName, Date expirationDate, String subAuthName) throws JFedException {
        GeniUrn userUrn = getRelevantUserUrn();
        String userHrn = PlanetlabSfaRegistryInterface.urnToSfaHrn(userUrn);

        String sliceHrn = subAuthName + "." + sliceName;
        List<String> researcherHrns = new ArrayList<>();
        researcherHrns.add(userHrn);

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

        List<AnyCredential> authCreds = authCredentialsByHrn.get(subAuthName);
        if (authCreds == null || authCreds.isEmpty()) {
            authCreds = getAuthCredentials(logger, userUrn, subAuthName);
            assert authCreds != null;
            assert !authCreds.isEmpty();
        }
        AnyCredential authCred = AnyCredential.toSingleCredential(authCreds);

        //fall back to default expiration time if none specified
        if (expirationDate == null) {
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.HOUR, 3);
            expirationDate = cal.getTime();
        }

        PlanetlabSfaRegistryInterface.SimpleApiCallReply<String/*gid*/> replyRegister = registry(logger).registerSlice(
                getCon(),
                authCred,
                sliceHrn/*hrn*/,
                null/*sliceUrl*/,
                expirationDate/*expires*/,
                researcherHrns/*researchers*/,
                null/*pis*/,
                null/*description*/);

        if (!replyRegister.getGeniResponseCode().isSuccess())
            throw new JFedException("Could not create slice: code=" +
                    replyRegister.getGeniResponseCode() + " output=" + replyRegister.getOutput(),
                    replyRegister
            );

        //TODO: the returned gid is normally, the slice certificate in the slice credential? Or is it the credential? check and skip next step if needed
        String res = replyRegister.getValue();
        List<AnyCredential> sliceCredentials;
        try {
            AnyCredential sliceCredential = AnyCredential.createSfa2("Register slice credential", res);
            sliceCredentials = new ArrayList<>();
            sliceCredentials.add(sliceCredential);
        } catch (CredentialException e) {
            //automatic fallback to requesting slice credential
            sliceCredentials = getSliceCredentials(logger, sliceUrn);
        }

        return new SliceInfo(sliceName, sliceUrn, sliceCredentials);
    }

    @Override
    public List<AnyCredential> renewSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull Date newExpirationDate) throws JFedException {
        //TODO check if planetlab supports this   (Update perhaps?)
        throw new UnsupportedOperationException();
    }

    @Override
    public List<String> getSshKeysForUser(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
        Map struct = getResolveReply(logger, userUrn);
        Object regSshKeys = struct.get("reg-keys");
        if (regSshKeys != null && regSshKeys instanceof Object[]) {
            return AbstractApi.apiSpecifiesListOfString(regSshKeys);
        }

        LOG.warn("No ssh keys returned (reg-keys), not even empty list. struct="+struct);
        return null;
    }


    @Override
    public SubAuthoritySupport getSubAuthoritySupport(@Nonnull Logger logger) throws JFedException {
        return SubAuthoritySupport.SUB_AUTHORITY_MANDATORY;
    }

    /* sub authority support */
    @Override
    public List<String> getSubAuthorityNames(@Nonnull Logger logger, @Nonnull GeniUrn user) throws JFedException {
        Set<String> res = new HashSet<>();
        if (user.getEncodedSubAuthName() != null)
            res.add(user.getEncodedSubAuthName());

        Map struct = getResolveReply(logger, user);
        Object regPiAuths = struct.get("reg-pi-authorities");
        if (regPiAuths != null && regPiAuths instanceof Object[]) {
            List<String> regPiAuthsList = AbstractApi.apiSpecifiesListOfString(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];
//                    res.add(regPiAuthNameEnd);
                    res.add(regPiAuthName);
                } else
                    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);

        return new ArrayList<>(res);
    }

    @Override
    public List<GeniUrn> getUsersForSubAuthority(@Nonnull Logger logger, @Nonnull String projectName) throws JFedException {
        //not sure if supported or not
        throw new RuntimeException("Not implemented");
    }

    @Override
    public List<GeniUrn> getUsersForSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        Map struct = getResolveReply(logger, sliceUrn);
        Object regUsers = struct.get("reg-researchers");
        if (regUsers != null && regUsers instanceof Object[]) {
            List<GeniUrn> res = new ArrayList<>();
            //noinspection unchecked
            for (String userHrn : AbstractApi.apiSpecifiesListOfString(regUsers)) {
                GeniUrn u = PlanetlabSfaRegistryInterface.sfaHrnToUserUrn(userHrn);
                if (u != null)
                    res.add(u);
                else
                    LOG.warn("Unsupported sfa user hrn: \""+userHrn+"\"");
            }
            return res;
        }

        LOG.warn("No slice users returned (reg-researchers), not even empty list. struct="+struct);
        return null;
    }

    @Override
    public void addUsersToSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull List<GeniUrn> userUrns, @Nonnull String role) throws JFedException {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public boolean isRegisterAggregatesForSliceSupported() {
        return false;
    }

    @Override
    public boolean hasSpeaksForSupport() {
        return false;
    }
}
