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

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.AggregateManager2;
import be.iminds.ilabt.jfed.lowlevel.api.AggregateManager3;
import be.iminds.ilabt.jfed.lowlevel.api.ProtogeniSliceAuthority;
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.rspec.request.geni_rspec_3.*;
import be.iminds.ilabt.jfed.testing.base.ApiTest;
import be.iminds.ilabt.jfed.util.SSHKeyHelper;
import be.iminds.ilabt.jfed.util.TargetAuthority;

import javax.inject.Inject;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * TestAggregateManager3ListResources
 */
public class TestAggregateManager3ListResources extends ApiTest {
    private ProtogeniSliceAuthority sa;
    private AggregateManager3 am3;

    private List<AnyCredential> userCredentials;
    private SSHKeyHelper sshKeyHelper;
    private Map versionRawResult = null;
    private AggregateManager3.VersionInfo versionInfo = null;

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

    public String getTestDescription() {
        return "Test ListResources call. ListResources can take some time and bandwidth. " +
                "This also tests all supported advertisement RSpec of an Aggregate Manager. A ListResources calls might be done for each.";
    }

    private List<AnyCredential> getUserCredentialList() {
        return userCredentials;
    }

    public SfaConnection getSAConnection() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(user, testedAuthority.getServerToConnect(), new ApiInfo.Api(ApiInfo.ApiName.PROTOGENI_SA, 1));
    }

    public SfaConnection getAM3Connection() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(user, user.getUserAuthorityServer(), new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, 3));
    }

    private boolean isValidGeni3AdvertisementRspec(String rspec) {
        if (rspec == null) return false;
        assertNotNull(rspec, "Rspec is null");

        int nodeCount = 0;
        int loginCount = 0;

        //parse the RSpec XML
        try {
            Class docClass = RSpecContents.class;
            String packageName = docClass.getPackage().getName();
            JAXBContext jc = JAXBContext.newInstance(packageName);
            Unmarshaller u = jc.createUnmarshaller();
            @SuppressWarnings("unchecked") JAXBElement<RSpecContents> doc = (JAXBElement<RSpecContents>) u.unmarshal(new StringReader(rspec));
            RSpecContents c = doc.getValue();

            RspecTypeContents typ = c.getType();
            assertEquals(typ.value(), "manifest", "Received manifest RSpec is not a manifest.");

            for (Object o : c.getAnyOrNodeOrLink()) {
                if (o instanceof JAXBElement) {
                    JAXBElement el = (JAXBElement) o;
                    if (el.getValue() instanceof NodeContents) {
                        NodeContents node = (NodeContents) el.getValue();

                        String nodeName = node.getClientId();
                        nodeCount++;

                        for (Object nodeElO : node.getAnyOrRelationOrLocation()) {
                            if (nodeElO instanceof JAXBElement) {
                                JAXBElement nodeEl = (JAXBElement) nodeElO;
//                                if (nodeEl.getValue() instanceof InterfaceContents) {
//                                    InterfaceContents ic = (InterfaceContents) nodeEl.getValue();
//                                    String interfaceName = ic.getClientId();
//                                }
                                if (nodeEl.getValue() instanceof ServiceContents) {
                                    ServiceContents serviceC = (ServiceContents) nodeEl.getValue();
                                    for (Object serviceElO : serviceC.getAnyOrLoginOrInstall()) {
                                        if (serviceElO instanceof JAXBElement) {
                                            JAXBElement serviceEl = (JAXBElement) serviceElO;
                                            if (serviceEl.getValue() instanceof LoginServiceContents) {
                                                LoginServiceContents loginSC =
                                                        (LoginServiceContents) serviceEl.getValue();

                                                String auth = loginSC.getAuthentication();

                                                String aHostname = loginSC.getHostname();
                                                int aPort = 0;
                                                if (loginSC.getPort() != null)
                                                    aPort = Integer.parseInt(loginSC.getPort());
                                                String aUsername =
                                                        loginSC.getOtherAttributes().get(new QName("username"));

                                                if (Objects.equals(auth, "ssh-keys")) {
                                                    assertEquals("ssh-keys", auth, "service login authentication must be ssh-keys for node " + nodeName);
                                                    assertNotNull(aHostname, "no hostname in service login for node " + nodeName);
                                                    assertNotNull(aUsername, "no username in service login for node " + nodeName);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
//                    if (el.getValue() instanceof LinkContents) {
//                        LinkContents link = (LinkContents) el.getValue();
//
//                        String linkname = link.getClientId();
//
//                        for (Object linkElO : link.getAnyOrPropertyOrLinkType()) {
//                            if (linkElO instanceof JAXBElement) {
//                                JAXBElement linkEl = (JAXBElement) linkElO;
//                                if (linkEl.getValue() instanceof InterfaceRefContents) {
//                                    InterfaceRefContents ic = (InterfaceRefContents) linkEl.getValue();
//                                    String interfaceName = ic.getClientId();
//                                }
//                            }
//                        }
//                    }
                }
            }
        } catch (JAXBException e) {
            throw new RuntimeException("Exception parsing manifest RSpec Xml: " + e.getMessage(), e);
        }

        return true;
    }

    private boolean isEmptyRspec(String rspec) {
        //no real good test
        return rspec.length() < 30;
    }

    /**
     * Check for correctness of a AM2 XmlRpc result. Should be tested for each reply received.
     */
    public void testAM3CorrectnessXmlRpcResult(Map res) {
        Object code = res.get("code");
        Object value = res.get("value");
        Object output = res.get("output");
        assertNotNull(code, "testAM3CorrectnessXmlRpcResult code == null in " + res);
        assertNotNull(value, "testAM3CorrectnessXmlRpcResult value == null in " + res);
        assertInstanceOf(code, Map.class, "testAM3CorrectnessXmlRpcResult code is not Map in " + res);
        Map codeHt = (Map) code;
        Object genicode = codeHt.get("geni_code");
        assertNotNull(genicode, "testAM3CorrectnessXmlRpcResult code does not contain \"geni_code\" in " + res);
        assertEquals(genicode.getClass(), Integer.class, "testAM3CorrectnessXmlRpcResult 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, "testAM3CorrectnessXmlRpcResult: while geni_code is non success (" + genicodevalue + "), output == null in " + res);
            assertEquals(output.getClass(), String.class, "testAM3CorrectnessXmlRpcResult: while geni_code is non success (" + genicodevalue + "), output is not String (it is " + output.getClass().getName() + " with value \"" + output.toString() + "\") in \"+res+\"");
        } else {
            if (output == null)
                note("testAM3CorrectnessXmlRpcResult: while geni_code is success (" + genicodevalue + "), output is ommited. This is allowed by the API.");
            if (output != null && !(output instanceof String))
                warn("testAM3CorrectnessXmlRpcResult: 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.");
        }
    }

    public void setUp() throws JFedException {


        System.out.println("Fetching User credential needed for AM tests");
        UserAndSliceApiWrapper credWrapper =
                getAutomaticUserAndSliceApiWrapperFactory().create();
        userCredentials =
                credWrapper.getUserCredentials(logger, user.getUserUrn());

        am3 = new AggregateManager3(logger, jFedPreferences);
    }

    @Test
    public void testGetVersionXmlRpcCorrectness() throws JFedException {
        AggregateManager3.AggregateManagerReply<AggregateManager3.VersionInfo> reply =
                am3.getVersion(getAM3Connection());

        testAM3CorrectnessXmlRpcResult(reply.getRawResult());

        //GetVersion is the only method where we have to check for something extra in the raw XMLRPC result
        versionRawResult = reply.getRawResult();
        assertNotNull(versionRawResult);

        Object ver =
                versionRawResult.get("geni_api"); //this is here for backward compatibility with v1. GetVersion returns this on all versions.
        assert (ver != null);
        assert (ver instanceof Integer);
        int v = (Integer) ver;
        assert (v == 2);

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

        versionInfo = reply.getValue();
        assertNotNull(versionInfo);
    }

    @Test(hardDepends = {"testGetVersionXmlRpcCorrectness"})
    public void testGetVersionResultCorrectness() throws JFedException {
        Map value = (Map) versionRawResult.get("value");

        //check if all required fields are present
        assertEquals(value.get("geni_api"), 3, "AM \"geni_api\" field in GetVersion reply is not version the Integer 3");

        String[] rspecVersionListnames = {"geni_request_rspec_versions", "geni_ad_rspec_versions"};
        for (String name : rspecVersionListnames) {
            assertNotNull(value.get(name), name + " not in version result");

            Object[] v = assertInstanceOf(value.get(name), Object[].class, name + " is not a List but a " + value.get(name).getClass().getName());
            assertTrue(v.length > 0, name + " array is empty");
            for (Object o : v) {
                assertTrue(o instanceof Map, name + " array should contain only Map not a " + o.getClass().getName());
                Map t = (Map) o;
                assertMapContainsNonemptyString(t, "type");
                assertMapContainsNonemptyString(t, "version");
                assertMapContainsString(t, "schema");
                assertMapContainsString(t, "namespace");
                Object extensionsO = t.get("extensions");
                assertNotNull(extensionsO);
                Object[] extensions =
                        assertInstanceOf(extensionsO, Object[].class, "value for extensions of " + name + " is not a String but a " + extensionsO.getClass().getName());;
                for (Object e : extensions)
                    assertTrue(e instanceof String, "an extension of \"+name+\" is not a string but a " + e.getClass().getName());
            }
        }

        note("Use the test in TestAggregateManager3 for a full test of GetVersion");
    }

    @DataProvider(name = "testListResourcesDataProvider")
    public Object[][] getTypeVersionList() {
        assert versionInfo != null;

        Object[][] res = new Object[versionInfo.getAdRspecVersions().size()][1];

        int i = 0;
        for (AggregateManager3.VersionInfo.RspecVersion rspecVersion : versionInfo.getAdRspecVersions()) {
            res[i++][0] = rspecVersion;
        }
        return res;
    }

    @Test(hardDepends = {"testGetVersionXmlRpcCorrectness", "testGetVersionResultCorrectness"},
            dataProvider = "testListResourcesDataProvider")
    public void testListAllResources(AggregateManager2.VersionInfo.RspecVersion rspecVersion) throws JFedException {
        note("Testing ListResources for type=" + rspecVersion.getType() + " version=" + rspecVersion.getVersion());

        //test with good credentials
        AggregateManager3.AggregateManagerReply<String> reply = am3.listResources(
                getAM3Connection(),
                getUserCredentialList(),
                rspecVersion.getType(),
                rspecVersion.getVersion(),
                true /*available*/,
                true /*compressed*/,
                null /*extra options*/);

        testAM3CorrectnessXmlRpcResult(reply.getRawResult());

        assertTrue(reply.getGeniResponseCode().isSuccess());
        if (rspecVersion.getType().equalsIgnoreCase("geni") && Objects.equals(rspecVersion.getVersion(), "3"))
            assertTrue(isValidGeni3AdvertisementRspec(reply.getValue()));
        assertTrue(!isEmptyRspec(reply.getValue()));

        //TODO test if same version as requested?
    }
}
