/*
 * 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.Security;
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.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
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.certificates.cryptoapi.CryptoAPIX509Certificate;
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.config.commands.voters.datapacks.beans.VerificationCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialInputDataPack;
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 VerificationCardSetCredentialDataPackGeneratorTest {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private VerificationCardSetCredentialDataPackGenerator _verificationCardSetCredentialDataPackGenerator;

    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 X509CertificateGenerator certificateGenerator,
            @Mocked final ScytlKeyStoreServiceAPI storesService, @Mocked final CryptoAPIRandomString cryptoRandomString,
            @Mocked final PrimitivesServiceAPI primitivesService, @Mocked final LoggingWriter logWriter,
            @Mocked final CryptoAPIX509Certificate certificate)
            throws ConfigurationException, IOException, GeneralCryptoLibException, JSONException {

        ElGamalServiceAPI elGamalService = new ElGamalService();

        _verificationCardSetCredentialDataPackGenerator =
            new VerificationCardSetCredentialDataPackGenerator(asymmetricService, certificateGenerator, storesService,
                cryptoRandomString, elGamalService, primitivesService);

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

        String verificationCardSetID = "98c4cb0e0fe1454c8db26f2e1ace05a8";

        String choiceCodesKeyString =
            "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"Fw==\",\"q\":\"Cw==\"},\"elements\":[\"Ag==\",\"Ag==\"]}}";
        String choiceCodesMessageKeyString = new JSONObject().put("publicKey", choiceCodesKeyString).toString();
        String choiceCodesKeysDelimiter = ";";
        String choiceCodesEncryptionKeyAsConcatenatedString = choiceCodesMessageKeyString + choiceCodesKeysDelimiter
            + choiceCodesMessageKeyString + choiceCodesKeysDelimiter + choiceCodesMessageKeyString
            + choiceCodesKeysDelimiter + choiceCodesMessageKeyString + choiceCodesKeysDelimiter;
        ElGamalPublicKey choiceCodesKey = ElGamalPublicKey.fromJson(choiceCodesKeyString);
        String alias = "elgamalencryptionpublickey";

        VerificationCardSetCredentialInputDataPack inputDataPack = createInputDataPack(alias);

        ElGamalPublicKey expectedCombinedKey =
            choiceCodesKey.combineWith(choiceCodesKey).combineWith(choiceCodesKey).combineWith(choiceCodesKey);

        new Expectations() {
            {
                certificate.getSerialNumber();
                times = 1;
                result = BigInteger.TEN;
            }
        };

        VerificationCardSetCredentialDataPack result =
            _verificationCardSetCredentialDataPackGenerator.generate(inputDataPack, verificationCardSetID,
                choiceCodesEncryptionKeyAsConcatenatedString, getCertificateProperties());

        Assert.assertEquals(result.getChoiceCodesEncryptionPublicKey(), expectedCombinedKey);
        for (ElGamalPublicKey nonCombinedChoiceCodeEncryptionPublicKey : result
            .getNonCombinedChoiceCodesEncryptionPublicKeys()) {
            Assert.assertEquals(nonCombinedChoiceCodeEncryptionPublicKey, choiceCodesKey);
        }
    }

    private Properties getCertificateProperties() throws IOException {

        String path = "properties/verificationCardSetX509Certificate.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 VerificationCardSetCredentialInputDataPack createInputDataPack(String alias) {

        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("publicKey", alias);
        credentialProperties.setCredentialType(CertificateParameters.Type.SIGN);
        credentialProperties.setName("verificationCardSet");
        credentialProperties.setParentName("servicesca");
        credentialProperties.setPropertiesFile("properties/verificationCardSetX509Certificate.properties");

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