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

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.Objects;

/**
 * AuthorityFinder
 */
public class AuthorityFinder {
    private static final Logger LOG = LoggerFactory.getLogger(AuthorityFinder.class);

    private final TestbedInfoSource model;
    private final JFedPreferences jFedPreferences;

    @Inject
    public AuthorityFinder(TestbedInfoSource model, JFedPreferences jFedPreferences) {
        this.model = model;
        this.jFedPreferences = jFedPreferences;
    }

    public int size() {
        return model.getServers().size();
    }

    public enum Purpose { CREATE_SLIVER, STITCHING, STATUS, DELETE, RECOVER, RECOVER_BOTH_FIRST, RECOVER_BOTH_SECOND, REQUEST_RSPEC, FIND_USERAUTH }

    private enum CentralBrokerArchitectureChoice { FORCE_TOP, FORCE_SUBAUTH, USE_GLOBAL_SETTING/*typically this is preference in testbeds.xml*/ }

    @Nullable
    public Server findByAnyUrn(@Nonnull String urnString, @Nonnull Purpose purpose) {
        return findByUrn(urnString, purpose, true/*ignoreTypeAndName*/);
    }
    @Nullable
    public Server findByUrn(@Nonnull String urnString, @Nonnull Purpose purpose) {
        return findByUrn(urnString, purpose, false/*ignoreTypeAndName*/);
    }
    @Nullable
    public Server findByUrn(@Nonnull String urnString, @Nonnull Purpose purpose, boolean ignoreTypeAndName) {
        GeniUrn u = GeniUrn.parse(urnString);
        if (u == null) {
            LOG.warn("Tried to lookup Server using the invalid URN \"" + urnString + "\" -> solving problem by returning null");
            return null;
        }
        return findByUrn(u, purpose, ignoreTypeAndName);
    }
    public Server findByUrn(@Nonnull GeniUrn urn, @Nonnull Purpose purpose) {
        return findByUrn(urn, purpose, false/*ignoreTypeAndName*/);
    }
    public Server findByAnyUrn(@Nonnull GeniUrn urn, @Nonnull Purpose purpose) {
        return findByUrn(urn, purpose, true/*ignoreTypeAndName*/);
    }
    public Server findByUrn(@Nonnull GeniUrn urn, @Nonnull Purpose purpose, boolean ignoreTypeAndName) {
        String prefenceForceExoSM = jFedPreferences.getString(CorePreferenceKey.PREF_TESTBEDSPECIFIC_EXOGENI_FORCE_EXOSM).trim().toLowerCase(); //AUTO, YES or NO
        String prefenceRecoverExoSM = jFedPreferences.getString(CorePreferenceKey.PREF_TESTBEDSPECIFIC_EXOGENI_RECOVER_METHOD).trim().toLowerCase(); //BOTH or SINGLE

        TestbedInfoSource.SubAuthMatchAllowed allowed;
        TestbedInfoSource.SubAuthMatchPreference preferred;
        CentralBrokerArchitectureChoice centralBrokerArchitectureChoice;
        switch (purpose) {
//            case CREATE_SLIVER: {
//                allowed = AuthorityListModel.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
//                centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.USE_GLOBAL_SETTING;
//                break;
//            }
            case STITCHING: {
                //Basically, the SCS chooses, we can't be flexible!!

                //this is only used in one case. See StichngData for details on how AM is chosen when stitching.
//                allowed = AuthorityListModel.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
//                centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.FORCE_TOP;
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY;
                centralBrokerArchitectureChoice = null; //nothing special for exogeni, so prefer exact
                break;
            }
            case RECOVER:
                //assert prefenceRecoverExoSM.equalsIgnoreCase("single");
                if (!prefenceRecoverExoSM.equalsIgnoreCase("single"))
                    LOG.warn("Expected "+CorePreferenceKey.PREF_TESTBEDSPECIFIC_EXOGENI_RECOVER_METHOD+" to be \"single\", but it is \""+prefenceRecoverExoSM+"\"");
                //fall through: same as STATUS
            case CREATE_SLIVER:
                //fall through: same as STATUS
            case DELETE:
                //fall through: same as STATUS
            case STATUS: {
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
                if (Objects.equals(prefenceForceExoSM, "auto"))
                    centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.USE_GLOBAL_SETTING;
                else
                    centralBrokerArchitectureChoice = Objects.equals(prefenceForceExoSM, "yes") ? CentralBrokerArchitectureChoice.FORCE_TOP : CentralBrokerArchitectureChoice.FORCE_SUBAUTH;
                break;
            }
            case RECOVER_BOTH_FIRST: {
                assert prefenceRecoverExoSM.equalsIgnoreCase("both");
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
                centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.FORCE_TOP;
                break;
            }
            case RECOVER_BOTH_SECOND: {
                assert prefenceRecoverExoSM.equalsIgnoreCase("both");
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
                centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.FORCE_SUBAUTH;
                break;
            }
            case REQUEST_RSPEC: {
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_ONLY_EXACT_SUBAUTHORITY;
                centralBrokerArchitectureChoice = CentralBrokerArchitectureChoice.FORCE_SUBAUTH;
                break;
            }
            case FIND_USERAUTH: {
                allowed = TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL;
                centralBrokerArchitectureChoice = null; //do nothing special for central broker architecture (exogeni)
                break;
            }
            default: throw new RuntimeException("BUG: Unimplemented functionality for "+purpose);
        }

        if (centralBrokerArchitectureChoice != null && isCentralBrokerArchitecture(urn)) {
                switch (centralBrokerArchitectureChoice) {
                    case FORCE_TOP: {
                        preferred = TestbedInfoSource.SubAuthMatchPreference.PREFER_TOPLEVEL;
                        break;
                    }
                    case FORCE_SUBAUTH: {
                        preferred = TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY;
                        break;
                    }
                    case USE_GLOBAL_SETTING: {
                        //use testbed.xml settings
                        Server centralBrokerAuth = model.getByUrn(
                                urn,
                                TestbedInfoSource.SubAuthMatchAllowed.ALLOW_OTHER_SUBAUTHORITY,
                                TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY,
                                false/*ignoreTypeAndName*/);
                        if (centralBrokerAuth != null && centralBrokerAuth.hasFlag(Server.Flag.preferToplevelOverSubauthority))
                            preferred = TestbedInfoSource.SubAuthMatchPreference.PREFER_TOPLEVEL;
                        else
                            preferred = TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY;
                        break;
                    }
                    default:
                        throw new RuntimeException("Unsupported centralBrokerArchitectureChoice=" + centralBrokerArchitectureChoice);
                }
        } else {
            preferred = TestbedInfoSource.SubAuthMatchPreference.PREFER_EXACT_SUBAUTHORITY;
        }

        return model.getByUrn(urn, allowed, preferred, ignoreTypeAndName);
    }

    /**
     * returns true if the provided URN is for a known authority with central broker architecture.
     * Any urn type can be provided: type and name of the URN are ignored.
     *
     * The authority must have CENTRAL_BROKER_AUTH, and it's sub authorities must have CENTRAL_BROKER_SUBAUTH for this to return true
     * */
    public boolean isCentralBrokerArchitecture(@Nonnull GeniUrn urn) {
        //this was: return urn.getTopLevelAuthority_withoutSubAuth().equalsIgnoreCase("exogeni.net");

        Server aMatch = model.getByUrn(
                urn,
                TestbedInfoSource.SubAuthMatchAllowed.ALLOW_TOPLEVEL,
                TestbedInfoSource.SubAuthMatchPreference.PREFER_TOPLEVEL,
                true/*ignoreTypeAndName*/);
        return aMatch != null && (aMatch.hasFlag(Server.Flag.centralBrokerArchitectureBrokerauth) || aMatch.hasFlag(Server.Flag.centralBrokerArchitectureSubauth));
    }

}
