/*
 * 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.PublicKey;
import java.security.Security;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.Properties;

import javax.crypto.SecretKey;

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.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
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.VotingCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardSetCredentialInputDataPack;
import com.scytl.products.ov.constants.Constants;
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 VotingCardSetCredentialDataPackGeneratorTest {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private VotingCardSetCredentialDataPackGenerator votingCardSetCredentialDataPackGenerator;

    private static AsymmetricService asymmetricService;

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

    @Test
    public void generate_credential_happy_path(@Mocked final AsymmetricServiceAPI asymmetricService,
            @Mocked final SymmetricServiceAPI symmetricService, @Mocked final CryptoAPIRandomString cryptoRandomString,
            @Mocked final X509CertificateGenerator certificateGenerator,
            @Mocked final ScytlKeyStoreServiceAPI storesService, @Mocked final KeyPair expectedKeyPair,
            @Mocked final PublicKey publicKey, @Mocked final SecretKey expectedSecretKey,
            @Mocked final CryptoAPIX509Certificate certificate, @Mocked final CryptoAPIScytlKeyStore expectedKeyStore,
            @Mocked final VotingCardSetCredentialDataPack dataPack, @Mocked final LoggingWriter logWriter)
            throws ConfigurationException, IOException, GeneralCryptoLibException {

        VotingCardSetCredentialInputDataPack expectedInputDataPack = createInputDataPack();

        final CredentialProperties expectedCredentialProperties = expectedInputDataPack.getCredentialProperties();
        final String expectedAlias = expectedCredentialProperties.obtainPrivateKeyAlias();
        final char[] expectedKeyStorePassword = "12345678901234567890123456".toCharArray();

        votingCardSetCredentialDataPackGenerator = new VotingCardSetCredentialDataPackGenerator(asymmetricService,
            certificateGenerator, storesService, cryptoRandomString, symmetricService, new PrimitivesService());

        // This has to be done because the annotation it's not executed without
        // Spring
        Deencapsulation.setField(votingCardSetCredentialDataPackGenerator, logWriter);

        byte[] encodedPubKey = "publicKeyEncoded".getBytes();

        new Expectations() {
            {
                asymmetricService.getKeyPairForSigning();
                result = expectedKeyPair;
                times = 1;

                publicKey.getEncoded();
                result = encodedPubKey;
                times = 1;

                dataPack.setVoteCastCodeSignerKeyPair(expectedKeyPair);
                result = null;
                times = 1;

                symmetricService.getSecretKeyForEncryption();
                result = expectedSecretKey;
                times = 1;

                certificateGenerator.generate((CertificateParameters) any, publicKey, (PrivateKey) any);
                result = certificate;

                certificate.getSubjectDn().getCommonName();
                result = "test";

                certificate.getSerialNumber();
                result = new BigInteger("32");

                dataPack.setCodesSecretKey(expectedSecretKey);
                result = null;
                times = 1;

                expectedKeyPair.getPublic();
                result = publicKey;
                times = 2;

                cryptoRandomString.nextRandom(26);
                result = new String(expectedKeyStorePassword);
                times = 1;

                dataPack.setPassword(expectedKeyStorePassword);
                result = null;
                times = 1;

                storesService.createKeyStore();
                result = expectedKeyStore;
                times = 1;

                dataPack.setKeyStore(expectedKeyStore);
                result = null;
                times = 1;

                expectedKeyStore.setSecretKeyEntry(expectedAlias, expectedSecretKey, expectedKeyStorePassword);
                result = null;
                times = 1;
            }
        };

        votingCardSetCredentialDataPackGenerator.generate(expectedInputDataPack, Constants.EMPTY,
            getCertificateProperties());
    }

    private Properties getCertificateProperties() throws IOException {

        String path = "properties/votingCardSetX509Certificate.properties";

        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 VotingCardSetCredentialInputDataPack createInputDataPack() {
        String eeid = "1234";
        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 credentialProperties = new CredentialProperties();
        credentialProperties.setAlias(new LinkedHashMap<>(1));
        credentialProperties.getAlias().put("secretkey", "codessk");
        credentialProperties.setCredentialType(CertificateParameters.Type.SIGN);
        credentialProperties.setName("votingCardSet");
        credentialProperties.setParentName("servicesca");
        credentialProperties.setPropertiesFile("properties/votingCardSetX509Certificate.properties");

        VotingCardSetCredentialInputDataPack inputDataPack =
            new VotingCardSetCredentialInputDataPack(credentialProperties);
        inputDataPack.setParentKeyPair(parentKeyPair);
        inputDataPack.setEeid(eeid);
        final ReplacementsHolder replacementsHolder = new ReplacementsHolder(eeid);
        inputDataPack.setReplacementsHolder(replacementsHolder);
        inputDataPack.setStartDate(startValidityPeriod);
        inputDataPack.setEndDate(endValidityPeriod);

        return inputDataPack;
    }
}
