package be.ehealth.technicalconnector.service.kgss;

import java.security.PrivateKey;
import java.util.Map;

import be.ehealth.technicalconnector.utils.*;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.w3c.dom.Element;

import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.exception.UnsealConnectorException;
import be.ehealth.technicalconnector.service.ServiceFactory;
import be.ehealth.technicalconnector.service.keydepot.KeyDepotManager.EncryptionTokenType;
import be.ehealth.technicalconnector.service.keydepot.KeyDepotManagerFactory;
import be.ehealth.technicalconnector.service.sts.security.Credential;
import be.ehealth.technicalconnector.service.sts.security.KeyStoreInfo;
import be.ehealth.technicalconnector.service.sts.security.impl.KeyStoreCredential;
import be.ehealth.technicalconnector.session.Session;
import be.fgov.ehealth.etee.crypto.utils.KeyManager;
import be.fgov.ehealth.etee.kgss._1_0.protocol.*;
import be.fgov.ehealth.technicalconnector.tests.junit.rule.SessionRule;

/**
 * Key Generation and Storage Service Integration Tests This set of tests shows the use of KGSS through the use of the Technical Connector.
 * 
 * @author EHP
 */
public class KGSServiceIntegrationTest {

    @ClassRule
    public static SessionRule rule = SessionRule.withActiveSession()
                                                .build();

    private String keystoreLocation = rule.getSessionProperty("test.keystore.location");

    private String keystoreAlias = rule.getSessionProperty("test.keystore.alias");

    private String keystorePassword = rule.getSessionProperty("test.keystore.password");


    /**
     * Request KGSS to generate a new key with a predefined Access Control List.
     * 
     * The GetNewKey methods required the following parameters: - Credential: the credentials used to secure the web service call - ETK of
     * KGSS, used to seal the request to KGSS - ETK of your application, used by KGSS to seal the response. - Decryption keys from your
     * keystore, needed by the eHealth Crypto Framework to seal and unseal data
     * 
     * The following steps are executed: 1. retrieve the credential 2a. retrieve the ETK of KGSS 2b. retrieve the ETK of your application 3.
     * Use the eHealth Crypto Framework to retrieve the decryption keys 4. Request new key from KGSS
     */
    @Test
    public void testGetNewKey() throws Exception {
        GetNewKeyResponseContent response = retrieveResponseForTest();

        /*
         * Verify the response
         */
        Assert.assertNotNull(response);
        Assert.assertNotNull(response.getNewKeyIdentifier());
    }

    @Test
    public void testGetNewKeyWithoutEtk() throws Exception {
        GetNewKeyResponseContent response = retrieveResponseForTest();

        /*
         * Verify the response
         */
        Assert.assertNotNull(response);
        Assert.assertNotNull(response.getNewKeyIdentifier());
    }


    private GetNewKeyResponseContent retrieveResponseForTest() throws Exception {

        /*
         * Create request
         */
        GetNewKeyRequestContent content = new GetNewKeyRequestContent();
        // set the etk of the sending application
        byte[] myEtk = KeyDepotManagerFactory.getKeyDepotManager()
                                             .getETK(EncryptionTokenType.HOLDER_OF_KEY)
                                             .getEncoded();
        content.setETK(myEtk);
        // add an Allowed Reader (retrieved from property file)
        CredentialType ct = new CredentialType();
        ct.setNamespace(rule.getSessionProperty("test.kgss.ns"));
        ct.setName(rule.getSessionProperty("test.kgss.name"));
        ct.getValues()
          .add(rule.getSessionProperty("test.kgss.value"));
        content.getAllowedReaders()
               .add(ct);

        ConnectorXmlUtils.dump(content);
        /*
         * 4. Request new key from KGSS Invoke the technical connector framework's KGSS Service's getNewKey operation
         */
        return invoke(content, GetNewKeyResponseContent.class);


    }


    @SuppressWarnings("unchecked")
    private <T> T invoke(Object request, Class<T> resultClass) throws Exception {

        // 1a. retrieve the credential. usually an eHealth certificate (retrieved from property file)
        Credential authentication = new KeyStoreCredential(keystoreLocation, keystoreAlias, keystorePassword);
        // 1b. retrieve the service credential for the STS call. usually an eHealth certificate (retrieved from property file)
        Credential service = new KeyStoreCredential(keystoreLocation, keystoreAlias, keystorePassword);
        // 2a. retrieve the ETK of KGSS (used to seal the request)
        byte[] kgssEtk = getKgssEtk();

        //
        // 3. Use the eHealth Crypto Framework to retrieve the decryption keys
        //
        // load in the keystoremanager to be able to easily work with the keystores
        KeyStoreInfo ksInfo = new KeyStoreInfo(keystoreLocation, keystorePassword.toCharArray(), keystoreAlias, keystorePassword.toCharArray());
        KeyStoreManager encryptionKeystoreManager = new KeyStoreManager(ksInfo.getKeystorePath(), ksInfo.getKeystorePassword());
        // use the eHealth Crypto Framework to get the decryption keys, it will need the keystore and the password for the private keys
        Map<String, PrivateKey> decryptionKeys = KeyManager.getDecryptionKeys(encryptionKeystoreManager.getKeyStore(), ksInfo.getKeystorePassword());


        KgssService kgss = ServiceFactory.getKgssService();
        if (request instanceof GetNewKeyRequestContent) {
            // 4. Request new key from KGSS Invoke the technical connector framework's KGSS Service's getNewKey operation
            return (T) kgss.getNewKey((GetNewKeyRequestContent) request, authentication, decryptionKeys, kgssEtk);
        } else if (request instanceof GetKeyRequestContent) {

            Element token = Session.getInstance()
                                   .getSession()
                                   .getSAMLToken()
                                   .getAssertion();
            // 4. Request new key from KGSS Invoke the technical connector framework's KGSS Service's getKey operation
            return (T) kgss.getKey((GetKeyRequestContent) request, authentication, service, token, decryptionKeys, kgssEtk);
        }
        return null;
    }

    /**
     * Retrieves a previously generated key from KGSS. The test will try to retrieve the key generated in the first test (testGetNewKey) The
     * GetKey operation is secured by means of a SAML Assertion / Secured Token, because of this a call to STS is needed to retrieve an SAML
     * Assertion / Secured Token.
     * 
     * The GetKey methods required the following parameters: - authentication credentials: the credentials used to authenticate against STS,
     * usually your eID - service credentials: the credentials used to sign each succeeding request to an STS secured eHealth web service,
     * usually an eHealth issued certificate - Secure Token: the token received from STS - ETK of KGSS, used to seal the request to KGSS -
     * ETK of your application, used by KGSS to seal the response. - Decryption keys from your keystore, needed by the eHealth Crypto
     * Framework to seal and unseal data
     * 
     * The following steps are executed: 1a. retrieve the authentication credential for the STS call 1b. retrieve the service credential for
     * the STS call 2. do the STS call to get an Secure Token 3a. retrieve the ETK of KGSS 3b. retrieve the ETK of your application 4. Use
     * the eHealth Crypto Framework to retrieve the decryption keys 5. retrieve the key from KGSS
     */

    @Test
    public void testGetKeyWithEtk() throws Exception {
        testGetKey(true);
    }

    @Test
    public void testGetKeyWithSymmKey() throws Exception {
        try {
            testGetKey(false);
        } catch (UnsealConnectorException e) {
            ConnectorExceptionUtils.processUnsealConnectorException(e);
        }
    }


    private void testGetKey(boolean useSystemEtk) throws Exception {

        GetNewKeyResponseContent responseToRetrieveKeyIdentifier = retrieveResponseForTest();
        byte[] keyIdentifier = responseToRetrieveKeyIdentifier.getNewKeyIdentifier();

        GetKeyRequestContent content = new GetKeyRequestContent();
        // set the etk of the sending application
        if (useSystemEtk) {
            byte[] myEtk = KeyDepotManagerFactory.getKeyDepotManager()
                                                 .getETK(EncryptionTokenType.HOLDER_OF_KEY)
                                                 .getEncoded();
            content.setETK(myEtk);
        }

        // set the key identifier of the key to retrieve
        content.setKeyIdentifier(keyIdentifier);


        GetKeyResponseContent response = invoke(content, GetKeyResponseContent.class);

        /*
         * Verify the response
         */
        // check the retrieved key
        Assert.assertNotNull(response.getKey());
    }

    private byte[] getKgssEtk() throws TechnicalConnectorException {
        // The values to use in the ETK Request
        String app = "KGSS";
        String type = "CBE";
        String value = "0809394427";
        return KeyDepotManagerFactory.getKeyDepotManager()
                                     .getEtk(IdentifierType.lookup(type, null, IdentifierType.ETKDEPOT), Long.valueOf(value), app)
                                     .getEncoded();
    }


}
