package be.iminds.ilabt.jfed.lowlevel.api.test;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractFederationApi;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractFederationApi1;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.testing.base.ApiTest;
import be.iminds.ilabt.jfed.util.TargetAuthority;

import javax.inject.Inject;
import java.util.*;

/**
 * AbstractFederationApi2Test
 */
public abstract class AbstractFederationApi2Test extends ApiTest {
    @Inject
    public AbstractFederationApi2Test(Logger logger, TargetAuthority testedAuthority, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, TestbedInfoSource testbedInfoSource, JFedPreferences jFedPreferences) {
        super(logger, testedAuthority, geniUserProvider, connectionProvider, testbedInfoSource, jFedPreferences);
    }


    /**
     * Check for correctness of a XmlRpc result. Should be tested for each reply received.
     * */
    public void checkCorrectnessXmlRpcResult(AbstractFederationApi1.FederationApiReply res) {
        Map raw = res.getRawResult();
        Object code = raw.get("code");
        Object value = raw.get("value");
        Object output = raw.get("output");
        assertNotNull(code, "Invalid XmlRpc reply: \"code\" not found");
        assertNotNull(value, "Invalid XmlRpc reply: \"value\" not found");
        assertNotNull(output, "Invalid XmlRpc reply: \"output\" not found");
        assertTrue(code instanceof Integer);
        assertTrue(output instanceof String);
    }

    protected AbstractFederationApi1.GetVersionResult getVersionResult;
    protected void checkGetVersion(
            AbstractFederationApi1.FederationApiReply<? extends AbstractFederationApi1.GetVersionResult> reply,
            AbstractFederationApi api,
            boolean isAuthority) throws JFedException {
        checkCorrectnessXmlRpcResult(reply);
        assertTrue(reply.getGeniResponseCode().isSuccess());

        getVersionResult = reply.getValue();
        assertTrue(getVersionResult.getFields() != null);

        //work with raw object below
        Object raw = reply.getRawResult().get("value");
        assertNotNull(raw, "get_version return value not found");
        Map<String, Object> htRes = assertStringObjectMap(raw);
        setErrorsNotFatal();

        String version = assertMapContainsNonemptyString(htRes, "VERSION");

//        Map fields = assertMapContainsMap(htRes, "FIELDS"); //not mandatory
        Object fieldsObj = htRes.get("FIELDS");
        Map fields = null;
        if (fieldsObj != null) {
            if (fieldsObj instanceof Map)
                fields = (Map) fieldsObj;
            else
                errorNonFatal("get_version FIELDS should be a dictionary (Map), but it is a "+fieldsObj.getClass().getName());
        }

        if (!isAuthority) {
            //registry: no ROLE or CREDENTIAL_TYPES
            assertFalse(htRes.containsKey("ROLES"), "Registry should not have ROLES field in get_version");
            assertFalse(htRes.containsKey("CREDENTIAL_TYPES"), "Registry should not have CREDENTIAL_TYPES field in get_version");
        } else {
            //MemberAuthority or SliceAuthority: require SERVICES and CREDENTIAL_TYPES
            List credentialTypes = assertMapContainsList(htRes, "CREDENTIAL_TYPES");
            assertNotEmpty(credentialTypes);

            List<String> services = assertMapContainsList(htRes, "SERVICES",String.class);
            if (services != null) {
                note("SERVICES advertised in get_version: "+services);
                for (String requiredService : api.getRequiredApiServices())
                    assertTrue(services.contains(requiredService), "The required service "+requiredService+" is not specified in the get_version SERVICES");
                List<String> knownServices = new ArrayList<>(api.getRequiredApiServices());
                knownServices.addAll(api.getOptionalApiServices());
                List<String> unknownServices = new ArrayList<>(services);
                unknownServices.removeAll(knownServices);
                assertEmpty(unknownServices, "Some services advertised in get_version SERVICES are unknown: "+unknownServices+"   (note: all known services: "+knownServices+")");
            }

            //List roles = assertMapContainsList(htRes, "ROLES"); //optional
        }

        setErrorsFatal();

        List<String> knownFieldValueKeys = new ArrayList<>();
        knownFieldValueKeys.add("OBJECT");
        knownFieldValueKeys.add("TYPE");
        knownFieldValueKeys.add("CREATE");
        knownFieldValueKeys.add("MATCH");
        knownFieldValueKeys.add("UPDATE");
        knownFieldValueKeys.add("PROTECT"); //only for MemberAuthority
        if (fields != null) {
            note("FIELDS advertised in get_version: "+fields.keySet());
            for (Object entryO : fields.entrySet()) {
                Map.Entry entry = (Map.Entry) entryO;
                assertInstanceOf(entry.getKey(), String.class, "FIELDS contains entry with non-String key: "+entry.getKey().getClass().getName());
                assertInstanceOf(entry.getValue(), Map.class, "FIELDS contains entry with non-Map value: "+entry.getValue().getClass().getName());
                String key = (String) entry.getKey();
                Map<String, Object> value = assertStringObjectMap(entry.getValue());

                setErrorsNotFatal();
                boolean startWithUnderscore = key.startsWith("_");

                if (isAuthority) assertTrue(value.containsKey("OBJECT"), "FIELD definition for field \""+key+"\" should contain OBJECT key");

                assertTrue(value.containsKey("TYPE"), "FIELD definition for field \""+key+"\" should contain TYPE key");

                //Not mandatory:
//                if (isAuthority) assertTrue(value.containsKey("CREATE"), "FIELD definition for field \""+key+"\" should contain CREATE key");
//                                 assertTrue(value.containsKey("MATCH"), "FIELD definition for field \""+key+"\" should contain MATCH key");
//                if (isAuthority) assertTrue(value.containsKey("UPDATE"), "FIELD definition for field \""+key+"\" should contain UPDATE key");

                if (value.containsKey("OBJECT") || !isAuthority) {
                    String objectName = (String) value.get("OBJECT");
                    if (objectName == null && !isAuthority)
                        objectName = "SERVICE";
                    List<String> requiredFields = new ArrayList<>(api.getMinimumFieldNames(objectName));
//                    note("DEBUG: requiredFields="+requiredFields+"  for OBJECT="+objectName+"  at FIELD="+key);
                    boolean isRequiredField = requiredFields.contains(key);
                    if (isRequiredField) {
                        //assertFalse(isRequiredField, "The FIELD \"" + key + "\" is a required field. Only supplementary fields should be listed in FIELDS");
                        note("The FIELD \"" + key + "\" is a required field. This means that some settings for this default field are being overridden.");
                    } else {
                        assertTrue(startWithUnderscore, "supplementary FIELDS should start with and underscore: " + key);
                    }

                    //PROTECT has a default, so it is not mandatory
//                    if (objectName.equals("MEMBER"))
//                        assertTrue(value.containsKey("PROTECT"), "FIELD definition for field \""+key+"\" should contain PROTECT key for OBJECT=MEMBER");
//                    else
                    if (!Objects.equals(objectName, "MEMBER"))
                        assertFalse(value.containsKey("PROTECT"), "FIELD definition for field \"" + key + "\" should NOT contain PROTECT key for OBJECT != MEMBER");
                }
                for (String vk : value.keySet()) {
                    Object vv = value.get(vk);
                    warnIfNot(knownFieldValueKeys.contains(vk), "Unknown field key " + vk + " for entry " + key);

                    if (Objects.equals(vk, "OBJECT")) {
                        //                        assertTrue(services.contains(vv), "The supported SERVICES specified in get_version do not support the OBJECT "+vv+" for FIELD "+key);
                        warnIfNot(api.getApiObjects().contains(vv), "Unknown OBJECT " + vv + " for FIELD " + key);
                    }

                    if (Objects.equals(vk, "TYPE")) {
                        try {
                            Enum e = AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.valueOf(vv+"");
                        } catch (IllegalArgumentException e) {
                            errorNonFatal("Unknown TYPE "+vv+" for FIELD "+key);
                        }
                    }

                    if (Objects.equals(vk, "PROTECT")) {
                        try {
                            Enum e = AbstractFederationApi1.GetVersionResult.FieldInfo.Protect.valueOf(vv+"");
                        } catch (IllegalArgumentException e) {
                            errorNonFatal("Unknown PROTECT value "+vv+" for FIELD "+key);
                        }
                    }

                    if (Objects.equals(vk, "CREATE")) {
                        try {
                            Enum e = AbstractFederationApi1.GetVersionResult.FieldInfo.CreationAllowed.valueOf((vv+"").replace(' ', '_'));
                        } catch (IllegalArgumentException e) {
                            errorNonFatal("Unknown CREATE value "+vv+" for FIELD "+key);
                        }
                    }
                }

                if (getVersionResult.getFields() != null)
                    assertTrue(getVersionResult.getFields().containsKey(key), "Bug in jFed: GetVersionResult does not contain failed that is defined in get_version: \""+key+"\". All fields in GetVersionResult="+getVersionResult.getFields().keySet());

                setErrorsFatal();
            }
        }
    }

    protected void addRequiredSupplementaryFieldsToCreate(Map<String, String> fields, String objectName) {
        if (getVersionResult != null) {
            Map<String, AbstractFederationApi1.GetVersionResult.FieldInfo> projectSupplementaryFields =
                    getVersionResult.getFieldsForObject(objectName);
            if (projectSupplementaryFields.size() > 0) {
                for (Map.Entry<String, AbstractFederationApi1.GetVersionResult.FieldInfo> e : projectSupplementaryFields.entrySet()) {
                    String fieldName = e.getKey();
                    AbstractFederationApi1.GetVersionResult.FieldInfo fieldInfo = e.getValue();
                    if (!fields.containsKey(fieldName) && Objects.equals(fieldInfo.getCreationAllowed(), AbstractFederationApi1.GetVersionResult.FieldInfo.CreationAllowed.REQUIRED)) {
                        List<AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType> fakeableTypes =
                                new ArrayList<>();
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.URN);
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.URL); //TODO add better dummy values for URN, URL, EMAIL?
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.UID);
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.STRING);
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.EMAIL);
                        fakeableTypes.add(AbstractFederationApi1.GetVersionResult.FieldInfo.FieldType.KEY);
                        if (fakeableTypes.contains(fieldInfo.getType()))
                            fields.put(fieldName, "dummy");
                        else
                            warn("Do not know how to add supplementary field '"+fieldName+"' which has CREATION=REQUIRED and is of TYPE="+fieldInfo.getType());
                    }
                }
            }
        }
    }

    protected void checkLookupCorrectness(AbstractFederationApi1.FederationApiReply reply) {
        setErrorsFatal();
        Object rawReplyValue = reply.getRawValue();
        assertInstanceOf(rawReplyValue, Map.class, "Lookup result should be a dictionary (=Map), but is of type " + rawReplyValue.getClass().getName());

        @SuppressWarnings("unchecked")
        Map<Object, Object> rawReplyValueHt = (Map<Object, Object>) rawReplyValue;

        setErrorsNotFatal();
        for (Map.Entry e: rawReplyValueHt.entrySet()) {
            Object rawKey = e.getKey();
            Object rawVal = e.getValue();
            assertInstanceOf(rawKey, String.class, "Lookup dictionary should contain dictionaries string (URN) to dictionary. But at least one key is not String but " +
                    rawKey.getClass().getName());
            assertInstanceOf(rawVal, Map.class, "Lookup dictionary should contain dictionaries string to dictionary (=Map). But at least one value is not dict but " +
                    rawVal.getClass().getName());
        }
        setErrorsFatal();
    }
}
