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.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.ui.commandline.ContextFile;
import be.iminds.ilabt.jfed.util.TargetAuthority;
import be.iminds.ilabt.jfed.util.TextUtil;

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

/**
 * TestDelegationAggregateManager2. Create a delegated credential, and test it on an AMv2
 *
 * This needs 2 users: extra options:
 *    username2 = ...
 *    passwordFilename2 = ...
 *    pemKeyAndCertFilename2 = ...
 *    userAuthorityUrn2 = ...
 *
 * User 2 will delegate to user 1
 * Then user 1 will call the AM with the delegated credential of user 2
 */
public class TestDelegationOnAggregateManager extends ApiTest {
    @Inject
    public TestDelegationOnAggregateManager(Logger logger, TargetAuthority testedAuthority, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, TestbedInfoSource testbedInfoSource, JFedPreferences jFedPreferences) {
        super(logger, testedAuthority, geniUserProvider, connectionProvider, testbedInfoSource, jFedPreferences);
    }

    public String getTestDescription() {
        return "Delegation or speaksfor credential generation and usage test. This test generates a delegated or speaksfor credentials, and uses it to call ListResources on an AM.";
    }

    private AggregateManager2 am2;
    private AggregateManager3 am3;
    private CommonAMTest commonAMTestUser1;
    private CommonAMTest commonAMTestUser2;


    private GeniUser secondUser;

    public SfaConnection getAMConnectionUser1() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(
                user,
                testedAuthority.getServerToConnect(),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, amVersion));
    }
    public SfaConnection getAMConnectionUser2() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(
                secondUser,
                testedAuthority.getServerToConnect(),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_AM, amVersion));
    }

    @Override
    public List<String> getOptConfKeys() {
        List<String> res = new ArrayList<>();
        res.add("useSpeaksFor"); //any boolean string   (default no: use delegation)
        res.add("speaksForOptionNames");
        res.add("passwordFilename2");
        res.add("amVersion");
        return res;
    }

    @Override
    public List<String> getReqConfKeys() {
        List<String> res = new ArrayList<>();
        res.add("userUrn2");
        res.add("pemKeyAndCertFilename2");
        res.add("userAuthorityUrn2");
        return res;
    }


    private SfaCredential userCredential1;
    private List<AnyCredential> getUser1CredentialList() {
        List<AnyCredential> res = new ArrayList<>();
        res.add(userCredential1);
        return res;
    }
    private SfaCredential userCredential2;
    private List<AnyCredential> getUser2CredentialList() {
        List<AnyCredential> res = new ArrayList<>();
        res.add(userCredential2);
        return res;
    }
    private SfaCredential userCredential2DelegatedToUser1;
    private List<AnyCredential> getUser2DelegatedToUser1CredentialList() {
        List<AnyCredential> res = new ArrayList<>();
        res.add(userCredential2DelegatedToUser1);
        return res;
    }

    private boolean delegation;
    private List<String> speakForOptionNames;
    private int amVersion = 2;
    public void setUp() throws CredentialException {
        Boolean delegationConfigParameter = TextUtil.objectToBoolean(getTestConfig().get("useSpeaksFor"));
        if (delegationConfigParameter != null) {
            delegation = !delegationConfigParameter;
            if (delegation)
                note("This test will test delegation");
            else
                note("This test will test speaks-for");
        } else {
            delegation = true;
            note("This test will test delegation");
        }

        if (getTestConfig().get("amVersion") != null) {
            amVersion = Integer.parseInt(getTestConfig().get("amVersion")+"");
            assertTrue(amVersion >= 2 && amVersion <= 3, "Unsupported AM version: "+amVersion);
        }

        note("Getting info for 2nd user");

        secondUser = null;
        try {
            secondUser = new ContextFile(getTestConfig(), testbedInfoSource, authorityFinder).getUser("2");
        } catch (ContextFile.ContextFileException e) {
            throw new RuntimeException("Exception extracting user 2 details from context file", e);
        }
        assertNotNull(secondUser);
        assertNotNull(secondUser.getUserAuthorityServer());


        assertNotNull(user);
        assertNotNull(user.getUserAuthorityServer());

        am2 = new AggregateManager2(logger, jFedPreferences);
        am3 = new AggregateManager3(logger, jFedPreferences);

        commonAMTestUser1 = new CommonAMTest(this, user.getUserAuthorityServer());
        commonAMTestUser2 = new CommonAMTest(this, secondUser.getUserAuthorityServer());

        if (getTestConfig().containsKey("speaksForOptionNames")) {
            String speaksForOptionNames = (String) getTestConfig().get("speaksForOptionNames");

            String[] speaksForOptionNamesArray = speaksForOptionNames.split(",");
            speakForOptionNames = new ArrayList<>();
            for (String speaksForOptionName : speaksForOptionNamesArray) {
                speaksForOptionName = speaksForOptionName.trim();
                speakForOptionNames.add(speaksForOptionName);
            }
            note("Setting speaksFor option names to use: "+ speakForOptionNames);
        } else {
            speakForOptionNames = new ArrayList<>();
            speakForOptionNames.add("speaking_for");
            speakForOptionNames.add("geni_speaking_for");
            note("Using default speaksFor option names to use: "+ speakForOptionNames);
        }
    }

    @Test(description = "Fetching User 1 Credential.")
    public void getUser1Credential() throws JFedException, CredentialException {
        GeniUser user1 = user;
        userCredential1 = (SfaCredential) commonAMTestUser1.getUserCredentialListForAM();

        assertNotNull(userCredential1);

        note("Credential owner urn:  \""+userCredential1.getOwnerUrn()+"\"");
        note("Credential target urn: \""+userCredential1.getTargetUrn()+"\"");
        note("Credential type:       \""+userCredential1.getType()+"\"");
        note("Credential expires:    \"" + userCredential1.getExpires() + "\"");
        note("Credential expires (parsed):    \"" + userCredential1.getExpiresDate() + "\"");

        assertTrue(userCredential1.check(testbedInfoSource));
        note("Credential for "+user1.getUserUrnString()+" passed check");
    }

    @Test(description = "Fetching User 2 Credential.")
    public void getUser2Credential() throws JFedException, CredentialException {
        userCredential2 = (SfaCredential) commonAMTestUser2.getUserCredentialListForAM();

        assertNotNull(userCredential2);

        note("Credential owner urn:  \""+userCredential2.getOwnerUrn()+"\"");
        note("Credential target urn: \""+userCredential2.getTargetUrn()+"\"");
        note("Credential type:       \""+userCredential2.getType()+"\"");
        note("Credential expires:    \"" + userCredential2.getExpires() + "\"");
        note("Credential expires (parsed):    \"" + userCredential2.getExpiresDate() + "\"");

        assertTrue(userCredential2.check(testbedInfoSource));
        note("Credential for "+secondUser.getUserUrnString()+" passed check");
    }


    @Test(description = "Create delegated or speaksfor credential", hardDepends = {"getUser2Credential"})
    public void createDelegatedOrSpeaksForCredential() throws JFedException, CredentialException {
        createDelegatedOrSpeaksForCredentialInternal();
    }
    public void createDelegatedOrSpeaksForCredentialInternal() throws JFedException, CredentialException {
        GeniUser user1 = user;
        GeniUser user2 = secondUser;

        assertNotNull(userCredential2);

        Date expires = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
        if (userCredential2.getExpiresDate() != null && expires.after(userCredential2.getExpiresDate())) {
            note("original credential expires in less than 24 hours (at "+userCredential2.getExpires()+" == "+userCredential2.getExpiresDate()+"), so using that expire time for the "+ (delegation?"delegated":"speaksfor")+ " credential");
            expires = userCredential2.getExpiresDate();
        }
        if (userCredential2.getExpiresDate() == null) {
            note("original credential expires is invalid date. Will create credential that expires in 1 hour");
            expires = new Date(System.currentTimeMillis() + 60 * 60 * 1000);
        }

        if (delegation) {
            note("Configured to create delegated credential");

            userCredential2DelegatedToUser1 = userCredential2.delegate(
                    user1.getUserUrnString()/*newOwnerUrn*/,
                    user1.getClientCertificateChain().get(0)/*newOwnerCert*/,
                    user2.getPrivateKey()/*originalOwnerPrivateKey*/,
                    expires/*expireDate*/,
                    "*"/*delegatedRights*/,
                    false/*canDelegate*/);
        }
        else {
            note("Configured to create speaks-for credential");

            userCredential2DelegatedToUser1 = SfaCredential.createSpeaksFor(
                    user2.getUserUrnString()/*spokenForUrn*/,
                    user1.getUserUrnString()/*speakerUrn*/,
                    user2.getClientCertificateChain().get(0)/*spokenForCert*/,
                    user1.getClientCertificateChain().get(0)/*speakerCert*/,
                    user2.getPrivateKey()/*spokenForPrivateKey*/,
                    expires/*expireDate*/,
                    "*"/*privilegeName*/,
                    false/*canDelegate*/);
        }

        assertNotNull(userCredential2DelegatedToUser1);

        note("Credential owner urn:  \""+userCredential2DelegatedToUser1.getOwnerUrn()+"\"");
        note("Credential target urn: \""+userCredential2DelegatedToUser1.getTargetUrn()+"\"");
        note("Credential type:       \""+userCredential2DelegatedToUser1.getType()+"\"");
        note("Credential expires:    \""+userCredential2DelegatedToUser1.getExpires()+"\"");

        assertTrue(userCredential2DelegatedToUser1.check(testbedInfoSource));
        note("Generated "+ (delegation?"delegated":"speaksfor")+ " credential passed check");
    }

    private Map versionRawResult = null;
    private AggregateManager2.VersionInfo versionInfo = null;
    @Test(description = "Testing GetVersion as user 1. This call requires no credential.", hardDepends = {"createDelegatedOrSpeaksForCredential"})
    public void getVersionUser1() throws JFedException {
        //ignore AM version here (because it's GetVersion)
        AggregateManager2.AggregateManagerReply<AggregateManager2.VersionInfo> reply = am2.getVersion(getAMConnectionUser1());

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

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

    @Test(description = "Testing ListResources as user 2. The (non-delegated and non-speaks-for) user 2 credential is used.", hardDepends = {"getVersionUser1"})
    public void listResourcesUser2() throws JFedException {
        ApiCallReply<String> reply = null;
        if (amVersion == 2) {
            AggregateManager2.AggregateManagerReply<String> am2reply = am2.listResources(
                    getAMConnectionUser2(),
                    getUser2CredentialList()/*credentialList*/,
                    "geni"/*rspecType*/,
                    "3"/*rspecVersion*/,
                    true/*available*/,
                    true/*compressed*/,
                    null/*sliceUrn*/,
                    null/*extraOptions*/);
            reply = am2reply;
        }
        if (amVersion == 3) {
            AggregateManager3.AggregateManagerReply<String> am3reply = am3.listResources(
                    getAMConnectionUser2(),
                    getUser2CredentialList()/*credentialList*/,
                    "geni"/*rspecType*/,
                    "3"/*rspecVersion*/,
                    true/*available*/,
                    true/*compressed*/,
                    null/*extraOptions*/);
            reply = am3reply;
        }

        assertNotNull(reply);

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

        String rspec = reply.getValue();
        assertNotNull(rspec);
    }

    @Test(description = "Testing ListResources as user 1 with user 2 delegated or speaksfor credential.", hardDepends = {"listResourcesUser2"/*, "getUser1Credential"*/})
    public void listResourcesDelegatedOrSpeaksFor() throws JFedException {
        listResourcesDelegatedOrSpeaksForInternal();
    }
    public void listResourcesDelegatedOrSpeaksForInternal() throws JFedException {
        ApiCallReply<String> reply = null;
        if (delegation) {
            note("Calling ListResource with delegated credential");
            if (amVersion == 2) {
                AggregateManager2.AggregateManagerReply<String> am2reply = am2.listResources(
                        getAMConnectionUser1(),
                        getUser2DelegatedToUser1CredentialList()/*credentialList*/,
                        "geni"/*rspecType*/,
                        "3"/*rspecVersion*/,
                        true/*available*/,
                        true/*compressed*/,
                        null/*sliceUrn*/,
                        null/*extraOptions*/);
                reply = am2reply;
            }
            if (amVersion == 3) {
                AggregateManager3.AggregateManagerReply<String> am3reply = am3.listResources(
                        getAMConnectionUser1(),
                        getUser2DelegatedToUser1CredentialList()/*credentialList*/,
                        "geni"/*rspecType*/,
                        "3"/*rspecVersion*/,
                        true/*available*/,
                        true/*compressed*/,
                        null/*extraOptions*/);
                reply = am3reply;
            }
        }
        else {
            note("Calling ListResource with speaks-for credential");
            GeniUser user2 = secondUser;
            Map<String, Object> extraOptions = new HashMap<>();
//            extraOptions.put("geni_experimenter_urn", user2.getUserUrnString());
            for (String speakForOptionName : speakForOptionNames)
                extraOptions.put(speakForOptionName, user2.getUserUrnString());
            List<AnyCredential> credList = getUser2DelegatedToUser1CredentialList();
            assertNotNull(userCredential2);
            credList.add(userCredential2);

            if (amVersion == 2) {
                AggregateManager2.AggregateManagerReply<String> am2reply = am2.listResources(
                        getAMConnectionUser1(),
                        credList/*credentialList*/,
                        "geni"/*rspecType*/,
                        "3"/*rspecVersion*/,
                        true/*available*/,
                        true/*compressed*/,
                        null/*sliceUrn*/,
                        extraOptions/*extraOptions*/);
                reply = am2reply;
            }
            if (amVersion == 3) {
                AggregateManager3.AggregateManagerReply<String> am3reply = am3.listResources(
                        getAMConnectionUser1(),
                        credList/*credentialList*/,
                        "geni"/*rspecType*/,
                        "3"/*rspecVersion*/,
                        true/*available*/,
                        true/*compressed*/,
                        extraOptions/*extraOptions*/);
                reply = am3reply;
            }
        }
        assertNotNull(reply);

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

        String rspec = reply.getValue();
        assertNotNull(rspec);
    }

    @Test(description = "Testing ListResources as user 1 with user 2 delegated or speaksfor credential. Test with different valid expire date formats",
            hardDepends = {"listResourcesUser2"/*, "getUser1Credential"*/},
            softDepends = {"listResourcesDelegatedOrSpeaksFor"})
    public void listResourcesDelegatedOrSpeaksForVariousExpireDatesDormats() throws JFedException, CredentialException {
        note("All dates used in the credential expires field of this test are normally valid RFC3339");

        SfaCredential.debug_expiredate_forceZinsteadOfZero = false;
        SfaCredential.debug_expiredate_forcezulu = true;
        SfaCredential.debug_expiredate_discardsubsecond = true;
        SfaCredential.debug_expiredate_smalltz = false;
        note("Testing with +00:00 instead of Z and SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = false;
        SfaCredential.debug_expiredate_discardsubsecond = false;
        SfaCredential.debug_expiredate_smalltz = true;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = false;
        SfaCredential.debug_expiredate_discardsubsecond = true;
        SfaCredential.debug_expiredate_smalltz = false;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = true;
        SfaCredential.debug_expiredate_discardsubsecond = false;
        SfaCredential.debug_expiredate_smalltz = false;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = true;
        SfaCredential.debug_expiredate_discardsubsecond = true;
        SfaCredential.debug_expiredate_smalltz = true;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = true;
        SfaCredential.debug_expiredate_discardsubsecond = false;
        SfaCredential.debug_expiredate_smalltz = true;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = false;
        SfaCredential.debug_expiredate_discardsubsecond = true;
        SfaCredential.debug_expiredate_smalltz = true;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        SfaCredential.debug_expiredate_forcezulu = false;
        SfaCredential.debug_expiredate_discardsubsecond = false;
        SfaCredential.debug_expiredate_smalltz = false;
        note("Testing with SfaCredential.forceZuluTimezone="+SfaCredential.debug_expiredate_forcezulu +
                " discardMilliSeconds="+SfaCredential.debug_expiredate_discardsubsecond+
                " lowerCase="+SfaCredential.debug_expiredate_smalltz);
        createDelegatedOrSpeaksForCredentialInternal();
        listResourcesDelegatedOrSpeaksForInternal();

        //set back to defaults to be safe
        SfaCredential.debug_expiredate_forceZinsteadOfZero = true;
        SfaCredential.debug_expiredate_forcezulu = true;
        SfaCredential.debug_expiredate_discardsubsecond = true;
        SfaCredential.debug_expiredate_smalltz = false;
    }
}
