/*
 * Copyright (c) eHealth
 */
package be.ehealth.technicalconnector.cryptolib;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Properties;

import org.bouncycastle.util.encoders.Base64;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.service.etee.domain.EncryptionToken;
import be.ehealth.technicalconnector.service.etee.impl.CryptoImpl;
import be.ehealth.technicalconnector.service.sts.security.Credential;
import be.ehealth.technicalconnector.service.sts.security.KeyStoreInfo;
import be.ehealth.technicalconnector.service.sts.security.impl.BeIDCredential;
import be.ehealth.technicalconnector.service.sts.security.impl.KeyStoreCredential;
import be.ehealth.technicalconnector.utils.ConnectorIOUtils;
import be.fgov.ehealth.etee.crypto.encrypt.DataSealer;
import be.fgov.ehealth.etee.crypto.encrypt.DataSealerBuilder;
import be.fgov.ehealth.etee.crypto.encrypt.EncryptionTokenFactory;
import be.fgov.ehealth.etee.crypto.policies.EncryptionPolicy;
import be.fgov.ehealth.etee.crypto.policies.OCSPPolicy;
import be.fgov.ehealth.etee.crypto.policies.SigningCredential;
import be.fgov.ehealth.etee.crypto.policies.SigningPolicy;
import be.fgov.ehealth.etee.crypto.utils.Iterables;
import be.fgov.ehealth.technicalconnector.tests.junit.rule.SessionRule;
import be.fgov.ehealth.technicalconnector.tests.utils.AssumeTools;
import be.fgov.ehealth.technicalconnector.tests.utils.TestPropertiesLoader;


/**
 * Unit test for {@link CryptoImpl}
 *
 * @author EHP
 */
public class EhealthCryptoLibTest {

    private static final Logger LOG = LoggerFactory.getLogger(EhealthCryptoLibTest.class);

    private static final String ETK_KGSS = "/etee/kgss_acc.etk";

    private static String eHealthCertificate;

    private static String eHealthCertificatePwd;

    private final String stringToSeal = "test";

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

    private static Properties props;

    @Before
    public void init() throws Exception {
        props = TestPropertiesLoader.getProperties("/be.ehealth.technicalconnector.test.properties");
        eHealthCertificate = props.getProperty("test.keystore.location");
        eHealthCertificatePwd = props.getProperty("test.keystore.password");
    }

    /**
     * Seals a string value and verifies that the result is not null The identity card is used here to initialise DataSealer with its
     * certificate
     */
    @Test
    public void sealWithEid() throws Exception {
        AssumeTools.isEIDEnabled();
        // Retrieve the credential. (from id card certificate)
        Credential credential = BeIDCredential.getInstance(stringToSeal, BeIDCredential.EID_SIGN_ALIAS);

        // Get a DataSealerFactory
        LOG.debug(credential.getPrivateKey()
                .getClass()
                .getName());
        KeyStore eid = credential.getKeyStore();
        DataSealer dataSealer = DataSealerBuilder.newBuilder()
                .addOCSPPolicy(OCSPPolicy.NONE)
                .addSigningPolicy(SigningPolicy.EID, retrieveSigningCredential(BeIDCredential.EID_AUTH_ALIAS, eid), retrieveSigningCredential(BeIDCredential.EID_SIGN_ALIAS, eid))
                .addPublicKeyPolicy(EncryptionPolicy.KNOWN_RECIPIENT)
                .addSecretKeyPolicy(EncryptionPolicy.UNKNOWN_RECIPIENT)
                .build();

        EncryptionToken kgss = new EncryptionToken(getKgssEtk());


        // Seal
        byte[] sealedcontent = dataSealer.seal(EncryptionTokenFactory.getInstance()
                .create(kgss.getEncoded()), stringToSeal.getBytes());

        String actual = new String(Base64.encode(sealedcontent));
        Assert.assertNotNull(actual);

    }

    /**
     * Seals a string value and verifies that the result is not null An eHealth certificate is used here to initialise DataSealer
     */
    @Test
    public void sealWithEhealthCert() throws Exception {
        // Retrieve the credential (retrieved from eHealth certificate)
        KeyStoreInfo ksInfo = new KeyStoreInfo(eHealthCertificate, eHealthCertificatePwd.toCharArray(), props.getProperty("test.keystore.alias"), eHealthCertificatePwd.toCharArray());
        Credential credential = new KeyStoreCredential(ksInfo);

        // Get a DataSealerFactory

        SigningCredential outerSigningCred = SigningCredential.create(credential.getPrivateKey(), Arrays.copyOf(credential.getCertificateChain(), credential.getCertificateChain().length, X509Certificate[].class));
        SigningCredential innerSigningcredential = outerSigningCred;

        DataSealer dataSealer = DataSealerBuilder.newBuilder()
                .addOCSPPolicy(OCSPPolicy.NONE)
                .addSigningPolicy(SigningPolicy.EHEALTH_CERT, outerSigningCred, innerSigningcredential)
                .addPublicKeyPolicy(EncryptionPolicy.KNOWN_RECIPIENT)
                .addSecretKeyPolicy(EncryptionPolicy.UNKNOWN_RECIPIENT)
                .build();

        EncryptionToken kgss = new EncryptionToken(getKgssEtk());
        DateTime startDate = new DateTime();

        // Seal
        byte[] sealedcontent = dataSealer.seal(EncryptionTokenFactory.getInstance().create(kgss.getEncoded()), stringToSeal.getBytes());
        DateTime endDate = new DateTime();
        Duration dur = new Duration(startDate.getMillis(), endDate.getMillis());
        LOG.debug(dur + "");
        String actual = new String(Base64.encode(sealedcontent));
        Assert.assertNotNull(actual);
    }

    /**
     * Helper method for GetNewKey and GetKey. Returns the ETK of KGSS
     */
    private InputStream getKgssEtk() throws TechnicalConnectorException {
        return ConnectorIOUtils.getResourceAsStream(ETK_KGSS);
    }


    private SigningCredential retrieveSigningCredential(String alias, KeyStore keyStore) {
        try {
            Certificate[] c = keyStore.getCertificateChain(alias);
            X509Certificate[] certificateChain = Arrays.copyOf(c, c.length, X509Certificate[].class);
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
            return SigningCredential.create(privateKey, Iterables.newList(certificateChain));
        } catch (KeyStoreException e) {
            throw new IllegalArgumentException("Given keystore hasn't been initialized", e);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("There is a problem with the Security configuration... Check if all the required security providers are correctly registered", e);
        } catch (UnrecoverableKeyException e) {
            throw new IllegalStateException("The private key with alias [" + alias + "] could not be recovered from the given keystore", e);
        } catch (NullPointerException e) {
            throw new IllegalArgumentException("The key store doesn't contain the required key with alias [" + alias + "]", e);
        }
    }


}
