/*
 * 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.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.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.HashSet;
import java.util.LinkedHashMap;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
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.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.oscore.logging.api.writer.LoggingWriter;
import com.scytl.products.ov.commons.beans.EncryptionParameters;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardCredentialInputDataPack;
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 com.scytl.products.ov.utils.FingerprintGenerator;

import mockit.Deencapsulation;
import mockit.Mocked;

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

    private VerificationCardCredentialDataPackGenerator verificationCardCredentialDataPackGenerator;

    private static AsymmetricService asymmetricService;

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

        generateVerificationKeyPairFile();
    }
    
    @AfterClass
    public static void cleanup() throws IOException {        
        FileUtils.deleteDirectory(Paths.get("").resolve(Constants.OFFLINE_DIRECTORY).toFile());
    }
    

    private static void generateVerificationKeyPairFile() throws GeneralCryptoLibException, IOException {
        final String verificationCardID = "5678";
        final String verificationCardSetID = "9999";

        ElGamalEncryptionParameters elGamalEncryptionParameters =
            new ElGamalEncryptionParameters(new BigInteger("23"), new BigInteger("11"), new BigInteger("2"));

        ElGamalKeyPair keyPair =
            new ElGamalService().getElGamalKeyPairGenerator().generateKeys(elGamalEncryptionParameters, 1);

        Path verificationCardsKeyPairsFilePath = Paths.get("").resolve(Constants.OFFLINE_DIRECTORY)
            .resolve(Constants.VERIFICATION_CARDS_KEY_PAIR_DIRECTORY).resolve(verificationCardSetID);

        verificationCardsKeyPairsFilePath.toFile().mkdirs();

        verificationCardsKeyPairsFilePath =
            verificationCardsKeyPairsFilePath.resolve(verificationCardID + Constants.KEY);

        Files.write(verificationCardsKeyPairsFilePath,
            (keyPair.getPrivateKeys().toJson() + System.lineSeparator() + keyPair.getPublicKeys().toJson())
                .getBytes(StandardCharsets.UTF_8));
    }

    @Test
    public void generate_credential_happy_path(@Mocked final AsymmetricServiceAPI asymmetricService,
            @Mocked final X509CertificateGenerator certificateGenerator,
            @Mocked final ScytlKeyStoreServiceAPI storesService, @Mocked final CryptoAPIRandomString cryptoRandomString,
            @Mocked final PrimitivesServiceAPI primitivesService,
            @Mocked final FingerprintGenerator fingerprintGenerator, @Mocked LoggingWriter loggingWriter)
            throws Exception {

        ElGamalServiceAPI elGamalService = new ElGamalService();

        verificationCardCredentialDataPackGenerator = new VerificationCardCredentialDataPackGenerator(asymmetricService,
            cryptoRandomString, certificateGenerator, storesService, elGamalService, fingerprintGenerator);

        Deencapsulation.setField(verificationCardCredentialDataPackGenerator, loggingWriter);

        EncryptionParameters encryptionParameters = new EncryptionParameters("23", "11", "2");
        VerificationCardCredentialInputDataPack inputDataPack = createInputDataPack();
        PrivateKey verificationCardIssuerPrivateKey = asymmetricService.getKeyPairForSigning().getPrivate();

        verificationCardCredentialDataPackGenerator.generate(inputDataPack, encryptionParameters, "1234", "5678",
            "9999", verificationCardIssuerPrivateKey, "pinepinepinepine".toCharArray(), Paths.get(""));
    }

    @Test
    public void generate_correct_verification_card_signature(
            @Mocked final X509CertificateGenerator certificateGenerator,
            @Mocked final ScytlKeyStoreServiceAPI storesService, @Mocked final CryptoAPIRandomString cryptoRandomString,
            @Mocked final FingerprintGenerator fingerprintGenerator, @Mocked LoggingWriter loggingWriter)
            throws Exception {

        ElGamalServiceAPI elGamalService = new ElGamalService();

        verificationCardCredentialDataPackGenerator = new VerificationCardCredentialDataPackGenerator(asymmetricService,
            cryptoRandomString, certificateGenerator, storesService, elGamalService, fingerprintGenerator);

        Deencapsulation.setField(verificationCardCredentialDataPackGenerator, loggingWriter);

        EncryptionParameters encryptionParameters = new EncryptionParameters("23", "11", "2");
        VerificationCardCredentialInputDataPack inputDataPack = createInputDataPack();
        final KeyPair keyPairForSigning = asymmetricService.getKeyPairForSigning();
        PrivateKey verificationCardIssuerPrivateKey = keyPairForSigning.getPrivate();

        String eeid = "1234";
        final String verificationCardID = "5678";
        final String verificationCardSetID = "9999";
        final String pin = "pinepinepinepine";

        final VerificationCardCredentialDataPack dataPack = verificationCardCredentialDataPackGenerator.generate(
            inputDataPack, encryptionParameters, eeid, verificationCardID, verificationCardSetID,
            verificationCardIssuerPrivateKey, pin.toCharArray(), Paths.get(""));

        final byte[] signature = dataPack.getSignatureVCardPubKeyEEIDVCID();
        PublicKey publicKeyFromSigningKeyPair = keyPairForSigning.getPublic();

        final byte[] eeidAsBytes = eeid.getBytes(StandardCharsets.UTF_8);
        final byte[] verificationCardIDAsBytes = verificationCardID.getBytes(StandardCharsets.UTF_8);
        byte[] publicKeyBytesFromVerificationCardKeyPair =
            dataPack.getVerificationCardKeyPair().getPublicKeys().toJson().getBytes(StandardCharsets.UTF_8);

        boolean verified = asymmetricService.verifySignature(signature, publicKeyFromSigningKeyPair,
            publicKeyBytesFromVerificationCardKeyPair, eeidAsBytes, verificationCardIDAsBytes);
        Assert.assertTrue(verified);

    }

    @Test
    public void publicKeyAsJsonAlwaysReturnsSameValue() throws Exception {

        ElGamalServiceAPI elGamalService = new ElGamalService();
        ElGamalEncryptionParameters params =
            new ElGamalEncryptionParameters(new BigInteger("23"), new BigInteger("11"), new BigInteger("2"));
        Set<String> jsons = new HashSet<>(1);
        final ElGamalKeyPair keyPair = elGamalService.getElGamalKeyPairGenerator().generateKeys(params, 1);
        for (int i = 0; i < 1000; i++) {
            final String json = keyPair.getPublicKeys().toJson();
            jsons.add(json);
            ElGamalPublicKey pb = ElGamalPublicKey.fromJson(json);
            jsons.add(pb.toJson());
        }

        jsons.forEach(str -> System.out.println("json : " + str));
        Assert.assertEquals(1, jsons.size());

    }

    private VerificationCardCredentialInputDataPack createInputDataPack() {

        String eeid = "1234";
        String start = "2015-06-01T10:15:30Z";
        String end = "2016-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("privateKey", "elgamalprivatekey");
        credentialProperties.setCredentialType(CertificateParameters.Type.SIGN);
        credentialProperties.setName("verificationCardSet");
        credentialProperties.setParentName("servicesca");
        credentialProperties.setPropertiesFile("properties/verificationCardSetX509Certificate.properties");

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