/*
 * 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;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
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.Arrays;
import java.util.List;
import java.util.Properties;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.scytl.keystore.factory.CryptoScytlKeyStoreWithPBKDF;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.encryption.params.generation.ElGamalEncryptionParameters;
import com.scytl.products.ov.readers.ConfigurationInputReader;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.EncryptionParametersLoader;
import com.scytl.products.ov.utils.X509CertificateLoader;

import mockit.Injectable;
import mockit.Tested;

public class VotersHolderInitializerTest {

    public static final String eeID = "314bd34dcf6e4de4b771a92fa3849d3d";

    private static final String NUMBER_VOTING_CARDS = "10";

    private static final String BALLOT_ID = "1234";

    private static final String BALLOT_BOX_ID = "5678";

    private static final String VOTING_CARD_SET_ID = "111111";

    private static final String VERIFICATION_CARD_SET_ID = "111111";

    private static final String VOTING_CARD_SET_ALIAS = "TESTALIAS";

    private static final String ELECTORAL_AUTHORITY_ID = "222222";

    private static final List<String> CHOICE_CODES_KEY = Arrays.asList("CHOICE_CODES_KEY");

    private static final String PRIME_ENCRYPTION_PRIVATE_PRIVATE_KEY_JSON = "primeEncryptionPrivaateKeyJson";

    private static final String PLATFORM_ROOT_CA_PEM = "platformRootCAPem";

    private static final String BASE_PATH = "src/test/resources/votingCardSet/";

    private static final String OUTPUT_PATH =
        BASE_PATH + "output/20150615125123199000000/314bd34dcf6e4de4b771a92fa3849d3d";

    private static final String ENRICHED_BALLOT_PATH = BASE_PATH + "input/enrichedBallot.json";

    private static final String NUMBER_CREDENTIALS_PER_FILE = "1000";

    private static final String NUMBER_OF_PROCESSORS = "2";

    private static final String TEST_EE_PROPS = BASE_PATH + "input/input.properties";

    private static ConfigObjectMapper _mapper;

    @Rule
    public ExpectedException exception = ExpectedException.none();

    CryptoScytlKeyStoreWithPBKDF keystore;

    private VotersHolderInitializer _votersHolderInitializer;

    @Injectable
    @Tested
    private ConfigObjectMapper _configObjectMapper;

    @Injectable
    @Tested
    private EncryptionParametersLoader _encryptionParametersLoader;

    @Injectable
    @Tested
    private ConfigurationInputReader _configurationInputReader;

    @Injectable
    @Tested
    private X509CertificateLoader _x509CertificateLoader;

    @Injectable
    @Tested
    private ScytlKeyStoreService _scytlKeyStoreService;

    @BeforeClass
    public static void setUp() throws IOException {

        Security.addProvider(new BouncyCastleProvider());

        _mapper = new ConfigObjectMapper();
    }

    @AfterClass
    public static void shutDown() {
        Security.removeProvider("BC");
    }

    @Test
    public void adapt_the_parameters_correctly() throws GeneralCryptoLibException, IOException {

        BigInteger p = new BigInteger("98410948467621226217939024562481745739029877894524580801342393875448933957899");
        BigInteger q = new BigInteger("49205474233810613108969512281240872869514938947262290400671196937724466978949");
        BigInteger g = new BigInteger("3");

        ElGamalEncryptionParameters expectedParams = new ElGamalEncryptionParameters(p, q, g);

        _votersHolderInitializer = new VotersHolderInitializer(_configurationInputReader, _x509CertificateLoader,
            _scytlKeyStoreService, _encryptionParametersLoader);

        Properties props = new Properties();
        try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(TEST_EE_PROPS))) {
            props.load(bufferedReader);
        }

        String start = (String) props.get("start");
        Integer validityPeriod = Integer.parseInt((String) props.get("validityPeriod"));

        // ISO_INSTANT format => 2011-12-03T10:15:30Z
        ZonedDateTime startValidityPeriod = ZonedDateTime.now(ZoneOffset.UTC);
        ZonedDateTime electionStartDate = ZonedDateTime.ofInstant(Instant.parse(start), ZoneOffset.UTC);
        ZonedDateTime endValidityPeriod = electionStartDate.plusYears(validityPeriod);

        VotersParametersHolder holder = new VotersParametersHolder(Integer.valueOf(NUMBER_VOTING_CARDS), BALLOT_ID,
            getBallot(), BALLOT_BOX_ID, VOTING_CARD_SET_ID, VERIFICATION_CARD_SET_ID, ELECTORAL_AUTHORITY_ID,
            Paths.get(OUTPUT_PATH), Integer.valueOf(NUMBER_CREDENTIALS_PER_FILE), Integer.valueOf(NUMBER_OF_PROCESSORS),
            eeID, startValidityPeriod, endValidityPeriod, Constants.EMPTY, VOTING_CARD_SET_ALIAS, CHOICE_CODES_KEY,
            PRIME_ENCRYPTION_PRIVATE_PRIVATE_KEY_JSON, PLATFORM_ROOT_CA_PEM, null);

        holder = _votersHolderInitializer.init(holder, getConfigurationFile());

        assertThat(holder.getEncryptionParameters().getP(), is(expectedParams.getP().toString()));
        assertThat(holder.getEncryptionParameters().getQ(), is(expectedParams.getQ().toString()));
        assertThat(holder.getEncryptionParameters().getG(), is(expectedParams.getG().toString()));

        final CryptoAPIX509Certificate credentialCACert = holder.getCredentialCACert();
        final PrivateKey credentialCAPrivKey = holder.getCredentialCAPrivKey();
        validateKeyPair(credentialCACert.getPublicKey(), credentialCAPrivKey);

        assertThat(holder.getVotingCardCredentialInputDataPack() != null, is(true));
        assertThat(
            holder.getVotingCardCredentialInputDataPack().getCredentialAuthProperties().getAlias().get("privateKey"),
            is("auth_sign"));
        assertThat(
            holder.getVotingCardCredentialInputDataPack().getCredentialSignProperties().getAlias().get("privateKey"),
            is("sign"));
        assertThat(holder.getVotingCardSetCredentialInputDataPack() != null, is(true));
        assertThat(
            holder.getVotingCardSetCredentialInputDataPack().getCredentialProperties().getAlias().get("secretKey"),
            is("codessk"));

    }

    private Ballot getBallot() {

        final Path ballotAbsolutePath = Paths.get(ENRICHED_BALLOT_PATH).toAbsolutePath();

        final File ballotFile = ballotAbsolutePath.toFile();

        final Ballot ballot = getBallotFromFile(ENRICHED_BALLOT_PATH, ballotFile);

        return ballot;
    }

    private Ballot getBallotFromFile(final String ballotPath, final File ballotFile) {
        Ballot ballot;
        try {
            ballot = _mapper.fromJSONFileToJava(ballotFile, Ballot.class);
        } catch (final IOException e) {
            throw new IllegalArgumentException(
                "An error occurred while mapping \"" + ballotPath + "\" to a Ballot: " + e.getMessage());
        }
        return ballot;
    }

    private void validateKeyPair(final PublicKey publicKey, final PrivateKey credentialCAPrivKey)
            throws GeneralCryptoLibException {

        final AsymmetricServiceAPI asymService = new AsymmetricService();

        final String testString = "word to be tested by foo hahaha bar";
        final byte[] encryptedTestString = asymService.encrypt(publicKey, testString.getBytes(StandardCharsets.UTF_8));

        final byte[] decryptedTestString = asymService.decrypt(credentialCAPrivKey, encryptedTestString);

        final String decrypted = new String(decryptedTestString, StandardCharsets.UTF_8);

        assertThat(testString.equals(decrypted), is(true));
    }

    private File getConfigurationFile() {
        return Paths.get("src/test/resources/", Constants.KEYS_CONFIG_FILENAME).toFile();
    }
}
