/*
 * 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.security.KeyPair;
import java.util.Properties;

import javax.crypto.SecretKey;

import org.apache.commons.configuration.ConfigurationException;

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.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
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.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.datapacks.beans.CertificateParameters;
import com.scytl.products.ov.datapacks.generators.CredentialDataPackGenerator;
import com.scytl.products.ov.datapacks.generators.X509CertificateGenerator;
import com.scytl.products.ov.utils.FingerprintGenerator;
import com.scytl.products.ov.utils.PasswordEncrypter;

public class VotingCardSetCredentialDataPackGenerator extends CredentialDataPackGenerator {

    private final SymmetricServiceAPI symmetricService;

    private final FingerprintGenerator fingerPrintGenerator;

    private final PasswordEncrypter passwordEncrypter;

    public VotingCardSetCredentialDataPackGenerator(final AsymmetricServiceAPI asymmetricService,
            final X509CertificateGenerator certificateGenerator, final ScytlKeyStoreServiceAPI storesService,
            final CryptoAPIRandomString cryptoRandomString, final SymmetricServiceAPI symmetricService,
            final PrimitivesServiceAPI primitivesService) {
        super(asymmetricService, cryptoRandomString, certificateGenerator, storesService);
        
        this.symmetricService = symmetricService;
        this.fingerPrintGenerator = new FingerprintGenerator(primitivesService);
        this.passwordEncrypter = new PasswordEncrypter(asymmetricService);
    }

    public VotingCardSetCredentialDataPack generate(final VotingCardSetCredentialInputDataPack inputDataPack,
            final String keyForProtectingKeystorePassword, final Properties votingCardSetCerificateProperties)
            throws IOException, ConfigurationException, GeneralCryptoLibException {

        final VotingCardSetCredentialDataPack dataPack = new VotingCardSetCredentialDataPack();

        KeyPair voteCastCodeSignerKeyPair;
        try {
            voteCastCodeSignerKeyPair = asymmetricService.getKeyPairForSigning();
            dataPack.setVoteCastCodeSignerKeyPair(voteCastCodeSignerKeyPair);
        } catch (Exception e) {
            // error generating the Vote Cast Code Signer key pair
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_VOTECASTCODE_SIGNER_KEYPAIR)
                    .objectId("verificationCardSetID").electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new CreateVotingCardSetException(e);
        }
        // Vote Cast Code Signer key pair successfully generated
        logWriter.log(Level.INFO,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_VOTECASTCODE_SIGNER_KEYPAIR_GENERATED)
                .electionEvent(inputDataPack.getEeid())
                .objectId("verificationCardSetID").user("adminID")
                .additionalInfo("pubkey", fingerPrintGenerator.generate(voteCastCodeSignerKeyPair.getPublic()))
                .createLogInfo());

        SecretKey codesSecretKey;
        try {
            // Codes Secret Key successfuly generated and stored
            codesSecretKey = symmetricService.getSecretKeyForEncryption();
            dataPack.setCodesSecretKey(codesSecretKey);
            logWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_CODESECRET_GENERATED_AND_STORED)
                    .electionEvent(inputDataPack.getEeid())
                    .objectId("verificationCardSetID").user("adminID").createLogInfo());
        } catch (Exception e) {
            // error generating the Codes Secret Key
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_CODESECRET)
                    .electionEvent(inputDataPack.getEeid())
                    .objectId("verificationCardSetID").user("adminID").additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());
            throw new CreateVotingCardSetException(e);
        }

        final CertificateParameters certificateParameters =
            getCertificateParameters(inputDataPack.getCredentialProperties(), inputDataPack.getStartDate(),
                inputDataPack.getEndDate(), inputDataPack.getReplacementsHolder(), votingCardSetCerificateProperties);

        try {
            final CryptoAPIX509Certificate certificate =
                createX509Certificate(inputDataPack, certificateParameters, voteCastCodeSignerKeyPair);
            dataPack.setVoteCastCodeSignerCertificate(certificate);
        } catch (Exception e) {
            // error - error generating the Vote Cast Code Signer certificate
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_VOTECASTCODE_CERTIFICATE)
                    .electionEvent(inputDataPack.getEeid())
                    .objectId("verificationCardSetID").user("adminID")
                    .additionalInfo("cert_cn",
                        dataPack.getVoteCastCodeSignerCertificate().getSubjectDn().getCommonName())
                .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new CreateVotingCardSetException(e);
        }
        // Vote Cast Code Signer X.509 certificate successfully generated
        logWriter
            .log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_VOTECASTCODE_SIGNER_CERTIFICATE_GENERATED)
                    .electionEvent(inputDataPack.getEeid())
                    .objectId("verificationCardSetID").user("adminID")
                    .additionalInfo("cert_cn",
                        dataPack.getVoteCastCodeSignerCertificate().getSubjectDn().getCommonName())
            .additionalInfo("cert_sn", dataPack.getVoteCastCodeSignerCertificate().getSerialNumber().toString())
            .createLogInfo());

        char[] keyStorePassword = cryptoRandomString.nextRandom(Constants.KEYSTORE_PW_LENGTH).toCharArray();
        dataPack.setPassword(keyStorePassword);

        final CryptoAPIScytlKeyStore keyStore = storesService.createKeyStore();
        dataPack.setKeyStore(keyStore);

        String alias = inputDataPack.getCredentialProperties().obtainSecretKeyAlias();
        keyStore.setSecretKeyEntry(alias, codesSecretKey, keyStorePassword);

        String encryptedKeyStorePassword;
        try {
            encryptedKeyStorePassword = passwordEncrypter.encryptPasswordIfEncryptionKeyAvailable(keyStorePassword,
                keyForProtectingKeystorePassword);
        } catch (GeneralCryptoLibException e) {
            throw new IOException("Exception while trying to encrypt codes secret key keystore password", e);
        }
        dataPack.setEncryptedPassword(encryptedKeyStorePassword);

        return dataPack;
    }
}
