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

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.AbacCredential;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.SfaCredential;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * ApiWrapper
 */
public abstract class ApiWrapper {
    private final static org.slf4j.Logger parentLOG = LoggerFactory.getLogger(ApiWrapper.class);

    @Nonnull
    protected final Logger logger;
    @Nonnull
    protected final GeniUserProvider geniUserProvider;
    @Nonnull
    protected final JFedConnectionProvider connectionProvider;
    @Nonnull
    protected final JFedPreferences jFedPreferences;

    /**
     * If this is not empty and not null, speaksFor is activated
     */
    protected List<AnyCredential> speaksForCredentials;
    /**
     * Optional URN of the user spoken for. May be null.
     */
    @Nullable
    protected GeniUrn speaksForUserUrn;

    protected ApiWrapper(@Nonnull Logger logger, @Nonnull GeniUserProvider geniUserProvider, @Nonnull JFedConnectionProvider connectionProvider, @Nonnull JFedPreferences jFedPreferences) {
        this.logger = logger;
        this.geniUserProvider = geniUserProvider;
        this.connectionProvider = connectionProvider;
        this.jFedPreferences = jFedPreferences;
    }

    /**
     * returns only the SfaCredentials in the list, discarding the ABAC and other credentials, unless they are speaksfor.
     */
    public List<AnyCredential> filterCredentials(@Nonnull List<AnyCredential> origCredentials) {
        List<AnyCredential> res = origCredentials.stream()
                .filter(cred -> cred instanceof SfaCredential || (cred instanceof AbacCredential && cred.isSpeaksFor()))
                .collect(Collectors.toList());

        if (res.isEmpty() && !origCredentials.isEmpty()) {
            parentLOG.warn("Unknown what is a good credential to use. Will just use first one received (type=" + origCredentials.get(0).getClass().getName() + ").");
            res.add(origCredentials.get(0));
        }

        parentLOG.debug("ApiWrapper.filterCredentials started with  " + origCredentials.size() + " credentials and ended with " + res.size());
        return res;
    }

    /*
     * activate speaksfor support for this Wrapper.
     *
     * NOTE: speaksFor credential itself can be supplied in 2 ways:
     *    - as part of argument list of credentials each option takes
     *    - using this option
     * these wrappers will prevent the exact same credential being sent twice.
     *    -> see addSpeaksForCredentials
     */
    public void setSpeaksFor(@Nullable List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn) {
        this.speaksForCredentials = speaksForCredentials == null ? Collections.emptyList() : speaksForCredentials;
        this.speaksForUserUrn = speakingForUrn;
    }

    public boolean isSpeaksFor() {
        return this.speaksForCredentials != null && !this.speaksForCredentials.isEmpty();
    }

    public List<AnyCredential> getSpeaksForCredentials() {
        return speaksForCredentials;
    }

    @Nullable
    public GeniUrn getSpeaksForUserUrn() {
        return speaksForUserUrn;
    }

    /**
     * Adds speaks_for credentials if needed and not yet added.
     */
    @Nonnull
    public List<AnyCredential> addSpeaksForCredentials(@Nonnull List<AnyCredential> origCredentials) {
        List<AnyCredential> res = new ArrayList<>(origCredentials);

        if (speaksForCredentials != null) {
            //prevent duplicates from being added
            List<AnyCredential> speaksForCredsToAdd =
                    speaksForCredentials.stream()
                            .filter(speaksForCredential ->
                                    res.stream().anyMatch(existingCred -> identicalCredentialXmls(speaksForCredential, existingCred)))
                            .collect(Collectors.toList());

            res.addAll(speaksForCredsToAdd);
        }

        parentLOG.debug("AggregateManagerWrapper.addSpeaksForCredentials started with  {} credentials and ended with {}", origCredentials.size(), res.size());
        return res;
    }

    private static boolean identicalCredentialXmls(@Nonnull AnyCredential a1, @Nonnull AnyCredential a2) {
        return Objects.equals(a1.getCredentialXml(), a2.getCredentialXml());
    }


    /**
     * returns only the SfaCredentials in the list, discarding the ABAC and other credentials, unless they are speaksfor.
     * Also adds speaks_for credentials if needed and not yet added.
     * <p>
     * this is the combination of filterCredentials and addSpeaksForCredentials
     */
    @Nonnull
    public List<AnyCredential> finalizeSliceCredentials(@Nonnull List<AnyCredential> origCredentials) {
        List<AnyCredential> res = filterCredentials(origCredentials);
        res = addSpeaksForCredentials(res);

        parentLOG.debug("AggregateManagerWrapper.finalizeSliceCredentials started with {} credentials and ended with {}" ,origCredentials.size(), res.size());

        return Collections.unmodifiableList(res);
    }

}
