/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.config.commands.voters.datapacks.generators;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.Properties;

import org.apache.commons.configuration.ConfigurationException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.scytl.keystore.factory.CryptoScytlKeyStoreWithPBKDF;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.oscore.logging.api.writer.LoggingWriter;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardCredentialInputDataPack;
import com.scytl.products.ov.datapacks.beans.CertificateParameters;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;
import com.scytl.products.ov.datapacks.generators.X509CertificateGenerator;
import com.scytl.products.ov.datapacks.helpers.ReplacementsHolder;

import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mocked;

public class VotingCardCredentialDataPackGeneratorTest {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private VotingCardCredentialDataPackGenerator _votingCardCredentialDataPackGenerator;

    private static AsymmetricService _asymmetricService;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {
        _asymmetricService = new AsymmetricService();
    }

    @Test
    public void generate_credential_happy_path(@Mocked final CryptoAPIX509Certificate certificate,
            @Mocked final AsymmetricServiceAPI asymmetricService,
            @Mocked final X509CertificateGenerator certificateGenerator,
            @Mocked final PrimitivesServiceAPI primitivesService, @Mocked final ScytlKeyStoreServiceAPI storesService,
            @Mocked final CryptoAPIRandomString cryptoRandomString, @Mocked final LoggingWriter logWriter,
            @Mocked final CryptoScytlKeyStoreWithPBKDF cryptoScytlKeyStoreWithPBKDF)
            throws ConfigurationException, IOException, GeneralCryptoLibException {

        _votingCardCredentialDataPackGenerator = new VotingCardCredentialDataPackGenerator(asymmetricService,
            certificateGenerator, storesService, primitivesService, cryptoRandomString);

        Deencapsulation.setField(_votingCardCredentialDataPackGenerator, logWriter);

        VotingCardCredentialInputDataPack votingCardCredentialInputDataPack = createInputDataPack();
        String encodedPem =
            ("-----BEGIN CERTIFICATE-----\nMIIDgDCCAmigAwIBAgIUJrb" + "/jNipTnw8IIHjlqBbCh0CFoQwDQYJKoZIhvcNAQEL"
                + "\nBQAwaTEjMCEGA1UEAwwaRWxlY3Rpb24gRXZlbnQgQ0EgMTAwMDAwMDAxFjAUBgNV"
                + "\nBAsMDU9ubGluZSBWb3RpbmcxEjAQBgNVBAoMCVN3aXNzUG9zdDEJMAcGA1UEBwwA"
                + "\nMQswCQYDVQQGEwJDSDAeFw0xNTA1MjcwNzAyNTdaFw0xNjA2MDExMDE1MzBaMGYx"
                + "\nIDAeBgNVBAMMF0F1dGhvcml0aWVzIENBIDEwMDAwMDAwMRYwFAYDVQQLDA1Pbmxp"
                + "\nbmUgVm90aW5nMRIwEAYDVQQKDAlTd2lzc1Bvc3QxCTAHBgNVBAcMADELMAkGA1UE"
                + "\nBhMCQ0gwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCE9BuQ8QzGJom9\n4"
                + "/O2Uvi9qpqtTMvNgFC8B7X96FNZ0wg7bdjTLQ0hzxTTpNJ4bRm9XRQS9P8Fh05B\naaqm5uEzh+Xb5Ark6c4EGSvQS/AsC5"
                + "/tiDepjXZZe7PY8TAq+NjJIh8bdMc+R9QZ\n24BLL"
                + "+NHJKz42OPqMenlmiKO2V9UONx5mQnRuRzA6Joz14wSGvyMiFHjA2JsnTSx"
                + "\nsPiEUQ9KFYhs8r1uPx7pVCFLudaXEwS0dmHMyrGBYKX6v+Doc0Ey2hWf6M2SdT63"
                + "\nWMB0hiLG0MOADEffE1WNNvugfHtUs093dIiKl03aoL5PMLOFVXEB2KAi1g/MqnbT"
                + "\nUXSZB1tpAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH"
                + "/\nMA0GCSqGSIb3DQEBCwUAA4IBAQBBUahcBSWEUvAqt9yVvLXQHAGsZEQm1K7ljhlq"
                + "\nBGh590k99xIQyGdLKepMxR0ZzjrKPJOMTFpbEapOUBMsiy1lfHofLkmnxYhPmSfc\nnPQHo"
                + "+jUsIgVpzrIk13G67iFUEAJtWYH5BhQGIUHf2fGveVPSdFtnVDd97en58xK\neACPMa0+0NxhsdV/NhFQTF7"
                + "/X0tEQaggWUxJUXYDFoezx5fLVgp9Zt7v7f/P0Zhe\ns3/sY/u/nYf3K4CYyHCwUBUBDbQPhPV9mYt/ktoZ"
                + "+1qiSxo5USnXJWnUSrhbOx+k\n+efy2K3m0WKcUz2ADszt08VkzdWEhPKBqfUqOB4Y4+3/q4Td\n-----END "
                + "CERTIFICATE-----\n");

        char[] keystorePasswordToUse = "Random password with more than 16 characters".toCharArray();
        new Expectations() {
            {
                certificate.getCertificate();
                result = PemUtils.certificateFromPem(encodedPem);
                times = 4;

                asymmetricService.getKeyPairForSigning();
                times = 2;

                certificate.getSubjectDn().getCommonName();
                times = 2;
                result = "subjectName";

                certificate.getSerialNumber();
                times = 2;
                result = new BigInteger("3232");

                cryptoScytlKeyStoreWithPBKDF.setPrivateKeyEntry(anyString, (PrivateKey) any, keystorePasswordToUse,
                    (Certificate[]) any);
                times = 2;

                cryptoScytlKeyStoreWithPBKDF.toJSON(keystorePasswordToUse);
                times = 1;
                result = "This is the keystore as a json";
            }
        };

        Properties credentailsSignCertificatePropertiesAsString =
            getCertificateProperties("properties/credentialSignX509Certificate.properties");
        Properties credentailsAuthCertificatePropertiesAsString =
            getCertificateProperties("properties/credentialAuthX509Certificate.properties");
        _votingCardCredentialDataPackGenerator.generate(votingCardCredentialInputDataPack,
            votingCardCredentialInputDataPack.getReplacementsHolder(), keystorePasswordToUse, "credentialID",
            "votingCardSetID", credentailsSignCertificatePropertiesAsString,
            credentailsAuthCertificatePropertiesAsString, certificate);
    }

    private Properties getCertificateProperties(String path) throws IOException {

        return getCertificateParameters(path);
    }

    private Properties getCertificateParameters(String path) throws IOException {

        final Properties props = new Properties();

        try (InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) {
            props.load(input);
        }

        return props;
    }

    private VotingCardCredentialInputDataPack createInputDataPack() {

        String eeid = "1234";
        String credentialID = "5678";
        String start = "2015-06-01T10:15:30Z";
        String end = "2116-12-03T10:15:30Z";
        ZonedDateTime startValidityPeriod = ZonedDateTime.ofInstant(Instant.parse(start), ZoneOffset.UTC);
        ZonedDateTime endValidityPeriod = ZonedDateTime.ofInstant(Instant.parse(end), ZoneOffset.UTC);
        endValidityPeriod = endValidityPeriod.plusYears(2);
        KeyPair parentKeyPair = _asymmetricService.getKeyPairForSigning();

        CredentialProperties credentialSignProperties = new CredentialProperties();
        credentialSignProperties.setAlias(new LinkedHashMap<>(1));
        credentialSignProperties.getAlias().put("privateKey", "sign");
        credentialSignProperties.setCredentialType(CertificateParameters.Type.SIGN);
        credentialSignProperties.setName("credentialSign");
        credentialSignProperties.setParentName("credentialsca");
        credentialSignProperties.setPropertiesFile("properties/credentialSignX509Certificate.properties");

        CredentialProperties credentialAuthProperties = new CredentialProperties();
        credentialAuthProperties.setAlias(new LinkedHashMap<>(1));
        credentialAuthProperties.getAlias().put("privateKey", "auth_sign");
        credentialAuthProperties.setCredentialType(CertificateParameters.Type.SIGN);
        credentialAuthProperties.setName("credentialAuth");
        credentialAuthProperties.setParentName("credentialsca");
        credentialAuthProperties.setPropertiesFile("properties/credentialAuthX509Certificate.properties");

        VotingCardCredentialInputDataPack inputDataPack =
            new VotingCardCredentialInputDataPack(credentialSignProperties, credentialAuthProperties);
        inputDataPack.setParentKeyPair(parentKeyPair);
        inputDataPack.setEeid(eeid);
        final ReplacementsHolder replacementsHolder = new ReplacementsHolder(eeid, credentialID);
        inputDataPack.setReplacementsHolder(replacementsHolder);
        inputDataPack.setStartDate(startValidityPeriod);
        inputDataPack.setEndDate(endValidityPeriod);
        return inputDataPack;
    }
}
