/*
 * 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.electionevent.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.ov.config.commands.electionevent.datapacks.beans.ElectionCredentialDataPack;
import com.scytl.products.ov.config.commands.electionevent.datapacks.beans.ElectionInputDataPack;
import com.scytl.products.ov.config.exceptions.CreateElectionEventException;
import com.scytl.products.ov.config.exceptions.specific.GenerateAdminBoardKeysException;
import com.scytl.products.ov.config.exceptions.specific.GenerateElectionEventCAException;
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.utils.FingerprintGenerator;
import com.scytl.products.ov.utils.PasswordEncrypter;

public class ElectionCredentialDataPackGenerator extends CredentialDataPackGenerator {

    private final FingerprintGenerator fingerPrintGenerator;

    private final PasswordEncrypter passwordEncrypter;

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

        fingerPrintGenerator = new FingerprintGenerator(primitivesService);

        passwordEncrypter = new PasswordEncrypter(asymmetricService);
    }

    public ElectionCredentialDataPack generate(final ElectionInputDataPack electionInputDataPack,
            final ReplacementsHolder replacementsHolder, final String name,
            final String keyForProtectingKeystorePassword, final Properties certificateProperties,
            final CryptoAPIX509Certificate... parentCerts)
            throws ConfigurationException, GeneralCryptoLibException, IOException {

        final ElectionCredentialDataPack dataPack = new ElectionCredentialDataPack();

        KeyPair keyPair;
        try {

            keyPair = asymmetricService.getKeyPairForSigning();
            dataPack.setKeyPair(keyPair);

            if (name.equals(Constants.CONFIGURATION_ELECTION_CA_JSON_TAG)) {
                // log success - Key Pairs successfully generated and stored
                logWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENEECA_SUCCESS_KEYPAIR_GENERATED_STORED)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("pubkey_id", fingerPrintGenerator.generate(keyPair.getPublic())).createLogInfo());
            } else if (name.equals(Constants.CONFIGURATION_ADMINBOARD_CA_JSON_TAG)) {
                // log success - key pair successfully generated and stored
                logWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENABK_SUCCESS_KEYPAIR_GENERATED_STORED)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("pubkey_id", fingerPrintGenerator.generate(keyPair.getPublic())).createLogInfo());
            } else if (name.equals(Constants.CONFIGURATION_BALLOTBOX_JSON_TAG)) {
                logWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENBB_SUCCESS_KEYPAIR_GENERATED_STORED)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("pubkey_id", fingerPrintGenerator.generate(keyPair.getPublic())).createLogInfo());
            }
        } catch (Exception e) {

            if (name.equals(Constants.CONFIGURATION_ELECTION_CA_JSON_TAG)) {
                // log error - Error generating the key pair for the CA Certificate
                logWriter.log(Level.ERROR,
                    new LogContent.LogContentBuilder()
                        .logEvent(ConfigGeneratorLogEvents.GENEECA_ERROR_GENERATING_KEYPAIR_CA_CERTIFICATE)
                        .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                        .additionalInfo("err_desc", e.getMessage()).createLogInfo());
                throw new GenerateElectionEventCAException(e);
            } else if (name.equals(Constants.CONFIGURATION_ADMINBOARD_CA_JSON_TAG)) {
                // log error - Error generating the key pair of the AB
                logWriter.log(Level.ERROR,
                    new LogContent.LogContentBuilder()
                        .logEvent(ConfigGeneratorLogEvents.GENABK_ERROR_GENERATING_KEYPAIR_AB)
                        .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                        .additionalInfo("err_desc", e.getMessage()).createLogInfo());
                throw new GenerateAdminBoardKeysException(e);
            } else if (name.equals(Constants.CONFIGURATION_BALLOTBOX_JSON_TAG)) {
                logWriter.log(Level.ERROR,
                    new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENBB_ERROR_GENERATING_KEYPAIR)
                        .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                        .additionalInfo("err_desc", e.getMessage()).createLogInfo());
                throw new GenerateAdminBoardKeysException(e);
            }

            throw new CreateElectionEventException(e);
        }

        CryptoAPIX509Certificate certificate;
        try {

            final CertificateParameters certificateParameters = getCertificateParameters(
                electionInputDataPack.getCredentialProperties(), electionInputDataPack.getStartDate(),
                electionInputDataPack.getEndDate(), replacementsHolder, certificateProperties);

            certificate = createX509Certificate(electionInputDataPack, certificateParameters, keyPair);
            dataPack.setCertificate(certificate);

            if (name.equals(Constants.CONFIGURATION_ADMINBOARD_CA_JSON_TAG)) {
                // log success - AB Certificate correctly generated
                logWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENABK_SUCCESS_AB_CERTIFICATE_GENERATED)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("cert_cn", dataPack.getCertificate().getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", dataPack.getCertificate().getSerialNumber().toString()).createLogInfo());
            } else if (name.equals(Constants.CONFIGURATION_BALLOTBOX_JSON_TAG)) {
                logWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENBB_SUCCESS_CERTIFICATE_GENERATED)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("cert_cn", dataPack.getCertificate().getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", dataPack.getCertificate().getSerialNumber().toString()).createLogInfo());
            }
        } catch (Exception e) {

            if (name.equals(Constants.CONFIGURATION_ADMINBOARD_CA_JSON_TAG)) {
                // log error - Error generating the AB certificate
                logWriter.log(Level.ERROR, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENABK_ERROR_GENERATING_AB_CERTIFICATE)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("cert_cn", dataPack.getCertificate().getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", dataPack.getCertificate().getSerialNumber().toString()).createLogInfo());
                throw new GenerateAdminBoardKeysException(e);
            } else if (name.equals(Constants.CONFIGURATION_BALLOTBOX_JSON_TAG)) {
                logWriter.log(Level.ERROR, new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENBB_ERROR_GENERATING_CERTIFICATE)
                    .electionEvent(electionInputDataPack.getEeid()).user("adminID")
                    .additionalInfo("cert_cn", dataPack.getCertificate().getSubjectDn().getCommonName())
                    .additionalInfo("cert_sn", dataPack.getCertificate().getSerialNumber().toString()).createLogInfo());
                throw new GenerateAdminBoardKeysException(e);
            }

            throw new CreateElectionEventException(e);
        }

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

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

        if (parentCerts != null) {

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

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

            setPrivateKeyToKeystore(keyStore, electionInputDataPack.getCredentialProperties().obtainPrivateKeyAlias(),
                keyPair.getPrivate(), keyStorePassword, certs);

        } else {

            setPrivateKeyToKeystore(keyStore, electionInputDataPack.getCredentialProperties().obtainPrivateKeyAlias(),
                keyPair.getPrivate(), keyStorePassword, certificate);

        }

        String encryptedKeyStorePassword = passwordEncrypter.encryptPasswordIfEncryptionKeyAvailable(keyStorePassword,
            keyForProtectingKeystorePassword);
        dataPack.setEncryptedPassword(encryptedKeyStorePassword);

        // Adding the start and end dates
        dataPack.setStartDate(electionInputDataPack.getElectionStartDate());
        dataPack.setEndDate(electionInputDataPack.getElectionEndDate());

        return dataPack;
    }
}
