/*
 * 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 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.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.oscore.logging.api.factory.LoggingFactory;
import com.scytl.products.oscore.logging.api.formatter.MessageFormatter;
import com.scytl.products.oscore.logging.api.writer.LoggingWriter;
import com.scytl.products.oscore.logging.core.factory.LoggingFactoryLog4j;
import com.scytl.products.oscore.logging.core.formatter.SplunkFormatter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardCredentialInputDataPack;
import com.scytl.products.ov.config.exceptions.specific.GenerateCredentialDataException;
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.datapacks.helpers.ReplacementsHolder;
import com.scytl.products.ov.logging.ExecutionTimeLogger;
import com.scytl.products.ov.utils.FingerprintGenerator;

public class VotingCardCredentialDataPackGenerator extends CredentialDataPackGenerator {

    public final FingerprintGenerator fingerprintGenerator;

    private LoggingWriter loggingWriter;

    private TransactionInfoProvider transactionInfoProvider = new TransactionInfoProvider();

    public VotingCardCredentialDataPackGenerator(final AsymmetricServiceAPI asymmetricService,
            final X509CertificateGenerator certificateGenerator, final ScytlKeyStoreServiceAPI storesService,
            final PrimitivesServiceAPI primitivesService, final CryptoAPIRandomString cryptoRandomString) {
        super(asymmetricService, cryptoRandomString, certificateGenerator, storesService);

        this.fingerprintGenerator = new FingerprintGenerator(primitivesService);

        final MessageFormatter formatter = new SplunkFormatter("OV", "CS", transactionInfoProvider);
        final LoggingFactory loggerFactory = new LoggingFactoryLog4j(formatter);
        this.loggingWriter = loggerFactory.getLogger("splunkable");
    }

    public VotingCardCredentialDataPack generate(final VotingCardCredentialInputDataPack inputDataPack,
            final ReplacementsHolder replacementsHolder, final char[] keyStorePassword, final String credentialID,
            final String votingCardSetID, Properties credentialSignCertificateProperties,
            final Properties credentialAuthCertificateProperties, final CryptoAPIX509Certificate... parentCerts)
            throws IOException, GeneralCryptoLibException, ConfigurationException {

        // It's a new thread, so we need to create a new trackId
        // when we have tenantId
        String tenant = "1";
        transactionInfoProvider.generate(tenant, Constants.LOGGING_CLIENT_ADDRESS, Constants.LOGGING_SERVER_ADDRESS);

        final VotingCardCredentialDataPack dataPack = new VotingCardCredentialDataPack();

        ExecutionTimeLogger timer = new ExecutionTimeLogger("generateVotingCardCredentialDataPack-Detailed");
        KeyPair keyPairSign;
        try {
            keyPairSign = asymmetricService.getKeyPairForSigning();

            String publicKeySignFingerprint = fingerprintGenerator.generate(keyPairSign.getPublic());

            loggingWriter.log(
                    Level.DEBUG,
                    new LogContent.LogContentBuilder()
                            .logEvent(ConfigGeneratorLogEvents.GENCREDAT_SUCCESS_CREDENTIAL_SIGNING_KEYPAIR_GENERATED)
                            .electionEvent(inputDataPack.getEeid())
                            .user("adminID")
                            .objectId(votingCardSetID).additionalInfo("c_id", credentialID)
                            .additionalInfo("pubkey_id", publicKeySignFingerprint).createLogInfo());

            dataPack.setKeyPairSign(keyPairSign);

        } catch (Exception e) {

            loggingWriter.log(
                Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENCREDAT_ERROR_GENERATING_CREDENTIAL_SIGNING_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID").objectId(votingCardSetID)
                    .additionalInfo("c_id", credentialID).additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateCredentialDataException(e);
        }
        timer.log("generateKeyPairForSigning");
        KeyPair keyPairAuth;
        try {
            keyPairAuth = asymmetricService.getKeyPairForSigning();

            String keyPairAuthFingerPrint = fingerprintGenerator.generate(keyPairAuth.getPublic());

            loggingWriter
                .log(
                    Level.DEBUG,
                    new LogContent.LogContentBuilder()
                        .logEvent(
                            ConfigGeneratorLogEvents.GENCREDAT_SUCCESS_CREDENTIAL_ID_AUTHENTICATION_KEYPAIR_GENERATED)
                            .electionEvent(inputDataPack.getEeid())
                            .user("adminID")
                            .objectId(votingCardSetID).additionalInfo("c_id", credentialID)
                        .additionalInfo("pubkey_id", keyPairAuthFingerPrint).createLogInfo());

            dataPack.setKeyPairAuth(keyPairAuth);

        } catch (Exception e) {


            loggingWriter.log(
                Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENCREDAT_ERROR_GENERATING_CREDENTIAL_ID_AUTHENTICATION_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID").objectId(votingCardSetID)
                    .additionalInfo("c_id", credentialID).additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateCredentialDataException(e);
        }
        timer.log("generateKeyPairForAuthentication");
        final CertificateParameters certificateParametersSign =
            getCertificateParameters(inputDataPack.getCredentialSignProperties(), inputDataPack.getStartDate(),
                inputDataPack.getEndDate(), replacementsHolder, credentialSignCertificateProperties);

        CryptoAPIX509Certificate certificateSign;
        try {
            certificateSign = createX509Certificate(inputDataPack, certificateParametersSign, keyPairSign);

            loggingWriter.log(
                Level.DEBUG,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENCREDAT_SUCCESS_CREDENTIAL_ID_SIGNING_CERTIFICATE_GENERATED)
                    .electionEvent(inputDataPack.getEeid()).user("adminID").objectId(votingCardSetID)
                    .additionalInfo("c_id", credentialID)
                    .additionalInfo("cert_cn", certificateSign.getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", certificateSign.getSerialNumber().toString()).createLogInfo());

            dataPack.setCertificateSign(certificateSign);

        } catch (Exception e) {

            loggingWriter.log(
                Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENCREDAT_ERROR_GENERATING_CREDENTIAL_ID_SIGNING_CERTIFICATE)
                    .electionEvent(inputDataPack.getEeid()).user("adminID").objectId(votingCardSetID)
                    .additionalInfo("c_id", credentialID)
                    .additionalInfo("cert_cn", certificateParametersSign.getUserSubjectCn())
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateCredentialDataException(e);
        }

        timer.log("signCertificate");
        CryptoAPIX509Certificate certificateAuth;

        final CertificateParameters certificateParametersAuth =
            getCertificateParameters(inputDataPack.getCredentialAuthProperties(), inputDataPack.getStartDate(),
                inputDataPack.getEndDate(), replacementsHolder, credentialAuthCertificateProperties);

        try {

            certificateAuth = createX509Certificate(inputDataPack, certificateParametersAuth, keyPairAuth);

            loggingWriter.log(
                Level.DEBUG,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        ConfigGeneratorLogEvents.GENCREDAT_SUCCESS_CREDENTIAL_ID_AUTHENTICATION_CERTIFICATE_GENERATED)
                        .electionEvent(inputDataPack.getEeid())
                        .user("adminID")
                        .objectId(votingCardSetID).additionalInfo("c_id", credentialID)
                    .additionalInfo("cert_cn", certificateAuth.getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", certificateAuth.getSerialNumber().toString()).createLogInfo());

            dataPack.setCertificateAuth(certificateAuth);

        } catch (Exception e) {

            loggingWriter.log(
                Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        ConfigGeneratorLogEvents.GENCREDAT_ERROR_GENERATING_CREDENTIAL_ID_AUTHENTICATION_CERTIFICATE)
                        .electionEvent(inputDataPack.getEeid())
                        .user("adminID")
                        .objectId(votingCardSetID).additionalInfo("c_id", credentialID)
                        .additionalInfo("cert_cn", certificateParametersAuth.getUserSubjectDn().getCommonName())
                        .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateCredentialDataException(e);
        }

        timer.log("certificateAuth");

        CryptoAPIScytlKeyStore keyStore = storesService.createKeyStore();

        final CryptoAPIX509Certificate[] certs;

        certs = new CryptoAPIX509Certificate[parentCerts.length + 1];

        certs[0] = certificateSign;
        System.arraycopy(parentCerts, 0, certs, 1, parentCerts.length);

        setPrivateKeyToKeystore(keyStore, inputDataPack.getCredentialSignProperties().obtainPrivateKeyAlias(),
            keyPairSign.getPrivate(), keyStorePassword, certs);

        certs[0] = certificateAuth;

        setPrivateKeyToKeystore(keyStore, inputDataPack.getCredentialAuthProperties().obtainPrivateKeyAlias(),
            keyPairAuth.getPrivate(), keyStorePassword, certs);

        dataPack.setKeystoreToBeSerialized(keyStore, keyStorePassword);

        timer.log("createKeyStores");
        return dataPack;
    }
}
