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

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Service;
import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.*;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
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.testing.base.ApiTest;
import be.iminds.ilabt.jfed.util.TargetAuthority;

import javax.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * SimpleTestAggregateManager2. One simple test of "Geni Aggregate Manager API v2"
 * <p/>
 * This is little more than a connectivity test and one basic test of XmlRpc and GetVersion.
 * <p/>
 * Use TestAggregateManager2 for a lot more tests of the "Geni Aggregate Manager API v2". (And also for more thorough tests of GetVersion)
 */
public class TestAnyServiceGetVersion extends ApiTest {
    private CommonAMTest commonAMTest;

    @Inject
    public TestAnyServiceGetVersion(Logger logger, TargetAuthority testedAuthority, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, TestbedInfoSource testbedInfoSource, JFedPreferences jFedPreferences) {
        super(logger, testedAuthority, geniUserProvider, connectionProvider, testbedInfoSource, jFedPreferences);
    }

    public String getTestDescription() {
        return "Calls GetVersion on any available Aggregate manager for the given testbed. This is mainly useful to test connectivity.";
    }

    @Override
    public List<String> getOptConfKeys() {
        List<String> res = new ArrayList<>();

        res.add("service_name");
        res.add("service_version");

        CommonAMTest.addOptionsForConnectionSshProxy(res);

        return res;
    }

    /**
     * Check for correctness of a AM2 XmlRpc result. Should be tested for each reply received.
     */
    public void testAM2CorrectnessXmlRpcResult(Map res) {
        Object code = res.get("code");
        Object value = res.get("value");
        Object output = res.get("output");
        assertNotNull(code, "testAM2CorrectnessXmlRpcResult code == null in " + res);
        assertNotNull(value, "testAM2CorrectnessXmlRpcResult value == null in " + res);
        assertInstanceOf(code, Map.class, "testAM2CorrectnessXmlRpcResult code is not Map in " + res + " (it is " + code.getClass().getName() + " with value \"" + code.toString() + "\")");

        Map codeHt = (Map) code;
        Object genicode = codeHt.get("geni_code");
        assertNotNull(genicode, "testAM2CorrectnessXmlRpcResult code does not contain \"geni_code\" in " + res);
        assertEquals(genicode.getClass(), Integer.class, "testAM2CorrectnessXmlRpcResult code.geni_code is not int in " + res);
        int genicodevalue = (Integer) genicode;

        //output should be present if code is not 0
        if (genicodevalue != 0) {
            assertNotNull(output, "testAM2CorrectnessXmlRpcResult: while geni_code is non success (" + genicodevalue + "), output == null in " + res);
            assertEquals(output.getClass(), String.class, "testAM2CorrectnessXmlRpcResult: while geni_code is non success (" + genicodevalue + "), output is not String (it is " + output.getClass().getName() + " with value \"" + output.toString() + "\") in \"+res+\"");
        } else {
            //this is a connectivity test, we don't warn here, we just make a note of it.
            if (output == null)
                note("testAM2CorrectnessXmlRpcResult: while geni_code is success (" + genicodevalue + "), output is ommited. This is allowed by the API.");
            else {
                if (output != null && !(output instanceof String))
                    note("testAM2CorrectnessXmlRpcResult: while geni_code is success (" + genicodevalue + "), output is not String (it is " + output.getClass().getName() + " with value \"" + output.toString() + "\"). This is allowed but not recommended by jFed.");
            }
        }
    }

    private URL preferredUrl = null;
    private ApiInfo.Api preferredApi = null;
    public void setUp() {
        String serviceName = getTestConfig().getProperty("service_name");
        String serviceVersion = getTestConfig().getProperty("service_version");
        if (serviceName != null)
            note("Will limit to service: "+serviceName);
        if (serviceVersion != null)
            note("Will limit to service version: "+serviceVersion);

        Server server = testedAuthority.getServerToConnect();
        Map<ApiInfo.Api, URL> urls = new HashMap<>();
        for (Service service : server.getServices()) {
            try {
                urls.put(new ApiInfo.Api(service.getApi(), service.getApiVersion()), new URL(service.getUrl()));
            } catch (MalformedURLException e) {
                note("Invalid URL in TestbedInfoSource -> will ignore", e);
            }
        }

        if (urls.isEmpty()) {
            fatalError("No URLs are known for the tested authority " + server.getDefaultComponentManagerUrn());
        } else
            note("The tested authority has "+urls.size()+" registered URLs");

        for (Iterator<Map.Entry<ApiInfo.Api, URL>> it = urls.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<ApiInfo.Api, URL> e = it.next();
            if (serviceName != null && !e.getKey().getName().name().equalsIgnoreCase(serviceName)) {
                it.remove(); continue;
            }
            if (serviceVersion != null && !e.getKey().getVersion().equalsIgnoreCase(serviceVersion)) {
                it.remove(); continue;
            }
        }

        if (urls.isEmpty()) {
            fatalError("Due to the service_name and/or service_version filter, no URLs are left to test.");
        }

        List<ApiInfo.Api> preferences = Arrays.asList(
                new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 3),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 2),
                new ApiInfo.Api(ApiInfo.ApiName.PROTOGENI_SA, 1),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_SA, 2),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_SA, 1),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_MA, 2),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_MA, 1),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH, 2),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH, 1),
                new ApiInfo.Api(ApiInfo.ApiName.PLANETLAB_SLICE_REGISTRY, 1),
                new ApiInfo.Api(ApiInfo.ApiName.PROTOGENI_CH, 1),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_SCS, 1)
        );

        for (ApiInfo.Api pref : preferences) {
            URL url = urls.get(pref);
            if (url != null) {
                preferredUrl = url;
                preferredApi = pref;
                break;
            }
        }
        if (preferredUrl == null) {
            Map.Entry<ApiInfo.Api, URL> e = urls.entrySet().iterator().next();
            preferredUrl = e.getValue();
            preferredApi = e.getKey();
        }

        note("Chosen servertype: "+ preferredApi);
        note("URL: "+preferredUrl);

        commonAMTest = new CommonAMTest(this, user.getUserAuthorityServer());
        commonAMTest.setupProxyForConnectionPool(getTestConfig());

        note("done");
    }

    private Map versionRawResult = null;

    @Test
    public void testGetVersion() throws JFedException {
        switch (preferredApi.getName()) {
            case GENI_AM: { testAMGetVersion(); break; }
            case GENI_CH:    //fall through
            case GENI_CH_MA: //fall through
            case GENI_CH_SA: { testFedApiGetVersion(); break; }
            case GENI_SCS: { testScsGetVersion(); break; }
            default: { fatalError("There is no known \"GetVersion\" test for the server type "+ preferredApi); break; }
        }
    }

    public void testScsGetVersion() throws JFedException {
        StitchingComputationService scs = new StitchingComputationService(getLogger(), getJFedPreferences());

        SfaConnection sfaCon = (SfaConnection) getConnectionProvider().getConnectionByAuthority(getUser(), getTestedAuthority().getServerToConnect(), preferredApi);
        StitchingComputationService.SCSReply<Map> reply = scs.getVersion(sfaCon);
        Map versionRawResult = reply.getRawResult();

        assertNotNull(versionRawResult);

        assertEquals(reply.getCode(), GeniAMResponseCode.GENIRESPONSE_SUCCESS.getCode(), "GeniResponse code is not SUCCESS (0)");

        Map<String, Object> replyHt = AbstractApi.apiSpecifiesMapStringToObject(versionRawResult);
        Map<String, Object> valueHt = AbstractApi.apiSpecifiesMapStringToObject(replyHt.get("value"));
        Object iface = valueHt.get("interface");
        assertEquals(iface, "scs");
//        Object versions = valueHt.get("geni_api_versions");
//        assertNotNull(versions);

        note("Test completed successfully.");
    }

    public void testFedApiGetVersion() throws JFedException {
        FederationSliceAuthorityApi2 sa = new FederationSliceAuthorityApi2(getLogger(), getJFedPreferences());

        SfaConnection sfaCon = (SfaConnection) getConnectionProvider().getConnectionByAuthority(getUser(), getTestedAuthority().getServerToConnect(), preferredApi);
        AbstractFederationApi.FederationApiReply<FederationSliceAuthorityApi2.GetVersionSAResult> reply = sa.getVersion(sfaCon);
        Map versionRawResult = reply.getRawResult();

        assertNotNull(versionRawResult);

        assertEquals(reply.getCode(), GeniAMResponseCode.GENIRESPONSE_SUCCESS.getCode(), "GeniResponse code is not SUCCESS (0)");

        Map<String, Object> replyHt = AbstractApi.apiSpecifiesMapStringToObject(versionRawResult);
        Map<String, Object> valueHt = AbstractApi.apiSpecifiesMapStringToObject(replyHt.get("value"));
        Object version = valueHt.get("VERSION");
        assertNotNull(version);

        note("Test completed successfully. Version is "+version+"");
    }

    public void testAMGetVersion() throws JFedException {
        AggregateManagerWrapper amWrapper = getAutomaticAggregateManagerWrapperFactory().create(logger, testedAuthority.getServerToConnect());
        versionRawResult = amWrapper.getVersion();

        testAM2CorrectnessXmlRpcResult(versionRawResult);

        assertNotNull(versionRawResult);

        Map<String, Object> replyHt = AbstractApi.apiSpecifiesMapStringToObject(versionRawResult);
        Map<String, Object> codeHt = AbstractApi.apiSpecifiesMapStringToObject(replyHt.get("code"));
        Integer genicodeHt = AbstractApi.apiSpecifiesInt(codeHt.get("geni_code"));

        assertEquals(genicodeHt, GeniAMResponseCode.GENIRESPONSE_SUCCESS.getCode(), "GeniResponse code is not SUCCESS (0)");

        Map<String, Object> valueHt = AbstractApi.apiSpecifiesMapStringToObject(replyHt.get("value"));
        Integer geniapiHt = AbstractApi.apiSpecifiesInt(replyHt.get("geni_api"));

        assertNotNull(geniapiHt);

        note("Test completed successfully. Version is"+geniapiHt+".\nAll Done\n");
    }
}
