/*
 * 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.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PrivateKey;

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.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.scytl.keystore.factory.CryptoScytlKeyStoreWithPBKDF;
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.writer.LoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
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.config.exceptions.specific.GenerateVerificationCardDataException;
import com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents;
import com.scytl.products.ov.config.logs.Loggable;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.datapacks.generators.X509CertificateGenerator;
import com.scytl.products.ov.utils.FingerprintGenerator;

/**
 *
 */
public class VerificationCardCredentialDataPackGenerator extends ElGamalCredentialDataPackGenerator {

    private final FingerprintGenerator fingerprintGenerator;

    @Loggable
    private LoggingWriter loggingWriter;

    private final TransactionInfoProvider transactionInfoProvider = new TransactionInfoProvider();

    public VerificationCardCredentialDataPackGenerator(final AsymmetricServiceAPI asymmetricService,
            final CryptoAPIRandomString cryptoRandomString, final X509CertificateGenerator certificateGenerator,
            final ScytlKeyStoreServiceAPI storesService, final ElGamalServiceAPI elGamalService,
            final FingerprintGenerator fingerprintGenerator) {

        super(asymmetricService, cryptoRandomString, certificateGenerator, storesService, elGamalService);

        this.fingerprintGenerator = fingerprintGenerator;
    }

    public VerificationCardCredentialDataPack generate(final VerificationCardCredentialInputDataPack inputDataPack,
            final EncryptionParameters encryptionParameters, final String eeid, final String verificationCardID,
            final String verificationCardSetID, final PrivateKey verificationCardIssuerPrivKey, final char[] pin,
            final Path absoluteBasePath) throws GeneralCryptoLibException {

        // 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 VerificationCardCredentialDataPack dataPack = new VerificationCardCredentialDataPack();

        final ElGamalKeyPair verificationCardKeypair = retrieveVerificationCardKeyPair(encryptionParameters, eeid,
            verificationCardID, verificationCardSetID, dataPack, absoluteBasePath);

        final byte[] signature = generateSignature(eeid, verificationCardID, verificationCardSetID,
            verificationCardIssuerPrivKey, verificationCardKeypair);

        dataPack.setSignatureVCardPubKeyEEIDVCID(signature);

        final CryptoScytlKeyStoreWithPBKDF keyStore = createCryptoKeyStoreWithPBKDF();
        saveToKeyStore(inputDataPack, keyStore, pin, verificationCardKeypair);
        dataPack.setKeystoreToBeSerialized(keyStore, pin);
        return dataPack;
    }

    private void saveToKeyStore(final VerificationCardCredentialInputDataPack inputDataPack,
            final CryptoScytlKeyStoreWithPBKDF keyStore, final char[] password,
            final ElGamalKeyPair verificationCardKeypair) throws GeneralCryptoLibException {

        putKeyInKeystore(keyStore, verificationCardKeypair.getPrivateKeys(), password,
            inputDataPack.getVerificationCardProperties().getAlias()
                .get(Constants.CONFIGURATION_VERIFICATION_CARD_PRIVATE_KEY_JSON_TAG));
    }

    private byte[] generateSignature(final String eeid, final String verificationCardID,
            final String verificationCardSetID, final PrivateKey verificationCardIssuerPrivKey,
            final ElGamalKeyPair verificationCardKeypair) {

        final byte[] eeidAsBytes = eeid.getBytes(StandardCharsets.UTF_8);
        final byte[] verificationCardIDAsBytes = verificationCardID.getBytes(StandardCharsets.UTF_8);

        final byte[] signature;
        try {
            final byte[] verificationCardPublicKeyAsBytes =
                verificationCardKeypair.getPublicKeys().toJson().getBytes(StandardCharsets.UTF_8);
            signature = asymmetricService.sign(verificationCardIssuerPrivKey, verificationCardPublicKeyAsBytes,
                eeidAsBytes, verificationCardIDAsBytes);
        } catch (GeneralCryptoLibException e) {

            loggingWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_VERIFICATION_CARD_PUBLIC_KEY)
                    .electionEvent(eeid).user("adminID").additionalInfo("verifcs_id", verificationCardSetID)
                    .additionalInfo("verifc_id", verificationCardID)
                    .additionalInfo("pubkey_id", fingerprintGenerator.generate(verificationCardKeypair.getPublicKeys()))
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateVerificationCardDataException(
                "An error occurred while trying to sign using the Verification Card Issuer Private Key: "
                    + e.getMessage(),
                e);
        }

        loggingWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_VERIFICATION_CARD_PUBLIC_KEY)
                .electionEvent(eeid).user("adminID").additionalInfo("verifcs_id", verificationCardSetID)
                .additionalInfo("verifc_id", verificationCardID)
                .additionalInfo("pubkey_id", fingerprintGenerator.generate(verificationCardKeypair.getPublicKeys()))
                .createLogInfo());
        return signature;
    }

    private ElGamalKeyPair retrieveVerificationCardKeyPair(final EncryptionParameters encryptionParameters,
            final String eeid, final String verificationCardID, final String verificationCardSetID,
            final VerificationCardCredentialDataPack dataPack, final Path absoluteBasePath) {

        try {
            Path verificationCardsKeyPairsFilePath = absoluteBasePath.resolve(Constants.OFFLINE_DIRECTORY)
                .resolve(Constants.VERIFICATION_CARDS_KEY_PAIR_DIRECTORY).resolve(verificationCardSetID)
                .resolve(verificationCardID + Constants.KEY);

            String[] verificationCardKeyPair =
                new String(Files.readAllBytes(verificationCardsKeyPairsFilePath), StandardCharsets.UTF_8)
                    .split(System.lineSeparator());
            
            dataPack
                .setVerificationCardKeyPair(new ElGamalKeyPair(ElGamalPrivateKey.fromJson(verificationCardKeyPair[0]),
                    ElGamalPublicKey.fromJson(verificationCardKeyPair[1])));

            loggingWriter.log(Level.DEBUG,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_VERIFICATION_CARD_KEYPAIR)
                    .electionEvent(eeid).user("adminID").additionalInfo("verifcs_id", verificationCardSetID)
                    .additionalInfo("verifc_id", verificationCardID)
                    .additionalInfo("pubkey_id",
                        fingerprintGenerator.generate(dataPack.getVerificationCardKeyPair().getPublicKeys()))
                    .createLogInfo());

        } catch (GeneralCryptoLibException | IOException e) {

            loggingWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_VERIFICATION_CARD_KEYPAIR)
                    .electionEvent(eeid).user("adminID").additionalInfo("verifcs_id", verificationCardSetID)
                    .additionalInfo("verifc_id", verificationCardID).additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());

            throw new GenerateVerificationCardDataException(
                "An error occurred while trying to create the Verification Card ElGamal Keypair: " + e.getMessage(), e);
        }

        return dataPack.getVerificationCardKeyPair();
    }
}
