/*
 * 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.channel.cc.choicecodes.generation.service;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;

import java.math.BigInteger;
import java.security.KeyManagementException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.proofs.proof.Proof;
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.SecureLoggingWriter;
import com.scytl.products.ov.channel.cc.commons.exponentiation.ExponentiationService;
import com.scytl.products.ov.channel.cc.commons.exponentiation.PowersAndProof;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyManager;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyNotFoundException;
import com.scytl.products.ov.channel.cc.commons.keymanagement.log.ControlComponentsCommonsLogConstants;
import com.scytl.products.ov.channel.cc.commons.keymanagement.log.ControlComponentsCommonsLogEvents;
import com.scytl.products.ov.channel.cc.commons.services.ChoiceCodesKeyRepository;
import com.scytl.products.ov.channel.cc.commons.services.compute.KeyDerivationService;
import com.scytl.products.ov.channel.cc.commons.slogger.ServiceTransactionInfoProvider;
import com.scytl.products.ov.channel.cc.commons.validations.MissingSignatureException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.InvalidSignatureException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignatureException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadVerificationException;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationDTO;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationInput;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationOutput;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationReqPayload;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationResPayload;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingException;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.messaging.Queue;
import com.scytl.products.ov.commons.sign.PayloadSigner;
import com.scytl.products.ov.commons.verify.PayloadVerifier;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;

@Service
public class ChoiceCodesGenerationServiceImpl implements ChoiceCodesGenerationService, MessageListener {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    private static final String CONFIRM_STRING_PADDING = "confirm";

    @Autowired
    private SecureLoggingWriter secureLoggingWriter;

    @Value("${keys.nodeId:defCcxId}")
    private String controlComponentId;

    private Queue computationInputQueue;

    private Queue computationOutputQueue;

    @Autowired
    private MessagingService messagingService;

    @Autowired
    private ExponentiationService exponentiationService;

    @Autowired
    private KeyDerivationService keyDerivationService;

    @Autowired
    private ChoiceCodesKeyRepository choiceCodesKeyRepository;

    @Autowired
    private ElGamalComputationsValuesCodec codec;

    @Autowired
    private PayloadSigner payloadSigner;

    @Autowired
    private PayloadVerifier payloadVerifier;

    @Autowired
    private KeyManager keyManager;

    @Autowired
    private ServiceTransactionInfoProvider serviceTransactionInfoProvider;

    @Autowired
    private PrimitivesServiceAPI primitivesService;

    @Value("${queues.cg-comp.req}")
    public void setComputationInputQueue(String queueName) {
        computationInputQueue = new Queue(queueName);
    }

    @Value("${queues.cg-comp.res}")
    public void setComputationOutputQueue(String queueName) {
        computationOutputQueue = new Queue(queueName);
    }

    @Override
    public void onMessage(Object message) {
        @SuppressWarnings("unchecked")
        ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto =
            (ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload>) message;
        serviceTransactionInfoProvider.generate(dto.getPayload().getTenantId(), "clientIP", "serverIP",
            dto.getRequestId());
        try {

            validateSignature(dto.getPayload());

            List<ChoiceCodeGenerationOutput> choiceCodeGenerationOutputList = new ArrayList<>();

            ElGamalPrivateKey privateKey = getGenerationPrivateKey(dto);
            ZpSubgroup group = privateKey.getGroup();
            String verificationCardSetId = dto.getPayload().getVerificationCardSetId();

            for (ChoiceCodeGenerationInput generationInput : dto.getPayload().getChoiceCodeGenerationInputList()) {                
                Exponent choiceCodesExponent = deriveChoiceCodesExponent(generationInput, privateKey, verificationCardSetId);
                List<ElGamalComputationsValues> representations = getRepresentations(generationInput);
                List<ElGamalComputationsValues> choiceCodes =
                        computeChoiceCodes(dto, generationInput, representations, group, choiceCodesExponent);
                ZpGroupElement choiceCodesKeyCommitment = deriveKey(group, choiceCodesExponent);
                
                Exponent ballotCastingKeyExponent = deriveBallotCastingKeyExponent(generationInput, privateKey, verificationCardSetId);
                ElGamalComputationsValues computedBallotCastingKey =
                    computeBallotCastingKey(dto, generationInput, group, ballotCastingKeyExponent);
                ZpGroupElement ballotCastingKeyExponentCommitment = deriveKey(group, ballotCastingKeyExponent);

                Map<ElGamalComputationsValues, ElGamalComputationsValues> computedRepresentations =
                    new LinkedHashMap<>();
                for (int i = 0; i < representations.size(); i++) {
                    computedRepresentations.put(representations.get(i), choiceCodes.get(i));
                }

                choiceCodeGenerationOutputList.add(new ChoiceCodeGenerationOutput(
                    generationInput.getVerificationCardId(), generationInput.getEncryptedBallotCastingKey(),
                    codec.encodeSingle(computedBallotCastingKey), codec.encodeMap(computedRepresentations),
                    Base64.getEncoder().encodeToString(choiceCodesKeyCommitment.toJson().getBytes(UTF_8)),
                    Base64.getEncoder().encodeToString(ballotCastingKeyExponentCommitment.toJson().getBytes(UTF_8))));
            }

            sendResponse(dto, choiceCodeGenerationOutputList);
        } catch (GeneralCryptoLibException | KeyManagementException | MessagingException | JsonProcessingException e) {
            LOG.error("Failed to handle choice code generation request.", e);
        } catch (PayloadSignatureException e) {
            LOG.error("Failed to sign the choice code generation response.", e);
        } catch (MissingSignatureException e) {
            LOG.error("Failed to find a signature in the received request.", e);
        } catch (InvalidSignatureException e) {
            LOG.error("The signature of the received request is invalid", e);
        } catch (PayloadVerificationException e) {
            LOG.error("Exception while trying to verify the signature of the received request.", e);
        }
    }

    @Override
    public void startup() throws ChoiceCodesGenerationException {
        try {
            messagingService.createReceiver(computationInputQueue, this);
        } catch (MessagingException e) {
            LOG.error("Failed to startup choice codes generation service.", e);
            throw new ChoiceCodesGenerationException("Failed to startup choice codes generation service.", e);
        }
    }

    @Override
    public void shutdown() throws ChoiceCodesGenerationException {
        try {
            messagingService.destroyReceiver(computationInputQueue, this);
        } catch (MessagingException e) {
            LOG.error("Failed to shutdown choice codes generation service.", e);
            throw new ChoiceCodesGenerationException("Failed to shutdown choice codes generation service.", e);
        }
    }

    private void validateSignature(ChoiceCodeGenerationReqPayload payload) throws MissingSignatureException, InvalidSignatureException, KeyNotFoundException, KeyManagementException, PayloadVerificationException {

        String payloadId = String.format("EEID:%s,VCSID:%s,ChunkID:%s", payload.getElectionEventId(), payload.getVerificationCardSetId(), payload.getChunkId());
        LOG.info("Checking the signature of payload {}...", payloadId);

        if (payload.getSignature() == null) {
            LOG.warn("REJECTED payload {} because it is not signed", payloadId);
            throw new MissingSignatureException(payloadId);
        }

        if (!payloadVerifier.isValid(payload, choiceCodesKeyRepository.getPlatformCACertificate())) {
            LOG.warn("REJECTED payload {} because the signature is not valid", payloadId);
            throw new InvalidSignatureException(controlComponentId, payloadId);
        }

        LOG.info("Signature of request {} accepted for generation", payloadId);
    }

    private ElGamalComputationsValues computeBallotCastingKey(
            ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto, ChoiceCodeGenerationInput generationInput,
            ZpSubgroup group, Exponent exponent) throws GeneralCryptoLibException, JsonProcessingException {
        PowersAndProof<ElGamalComputationsValues> powersAndProof = null;
        ElGamalComputationsValues encryptedBck = null;
        encryptedBck = codec.decodeSingle(generationInput.getEncryptedHashedBallotCastingKey());
        try {
            List<ElGamalComputationsValues> encryptedBcks = singletonList(encryptedBck);
            powersAndProof = exponentiationService.exponentiateCiphertexts(encryptedBcks, exponent, group);
        } catch (GeneralCryptoLibException e) {
            // [SL GENPVCC-10]
            logErrorPartialVoteCastCodeCannotBeComputed(dto, generationInput, encryptedBck);
            throw e;
        }
        ElGamalComputationsValues computedBck = powersAndProof.powers().get(0);
        logBallotCastingKeySuccessfullyComputed(dto, generationInput, encryptedBck, computedBck);
        logBallotCastingKeyExponentiationProofSuccessfullyComputed(dto, generationInput, encryptedBck, computedBck,
            powersAndProof.proof());
        return computedBck;
    }

    /**
     * @param dto
     * @param generationInput
     * @param encryptedBck
     * @throws GeneralCryptoLibException
     * @throws JsonProcessingException
     */
    private void logErrorPartialVoteCastCodeCannotBeComputed(
            final ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto,
            final ChoiceCodeGenerationInput generationInput, ElGamalComputationsValues encryptedBck)
            throws GeneralCryptoLibException, JsonProcessingException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        String hashBck = ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValues(encryptedBck));
        secureLoggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder()
                .logEvent(ControlComponentsCommonsLogEvents.ERROR_PARTIAL_VOTE_CAST_CODE_NOT_COMPUTED_GEN)
                .user(verificationCardId).objectId(verificationCardSetId).electionEvent(electionEventId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.HASH_ENCCM, hashBck)
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .additionalInfo(ControlComponentsCommonsLogConstants.ERR_DESC,
                    ControlComponentsCommonsLogEvents.ERROR_PARTIAL_VOTE_CAST_CODE_NOT_COMPUTED_GEN.getInfo())
                .createLogInfo());
    }

    private List<ElGamalComputationsValues> computeChoiceCodes(
            ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto, ChoiceCodeGenerationInput generationInput,
            List<ElGamalComputationsValues> representations, ZpSubgroup group, Exponent exponent)
            throws GeneralCryptoLibException, JsonProcessingException {
        PowersAndProof<ElGamalComputationsValues> powersAndProof = null;
        try {
            powersAndProof = exponentiationService.exponentiateCiphertexts(representations, exponent, group);
        } catch (GeneralCryptoLibException e) {
            // [SL GENPCC-10]
            logErrorComputingPartialChoiceCodeGEN(dto, generationInput);
            throw e;
        }
        List<ElGamalComputationsValues> computedRepresentations = powersAndProof.powers();
        logPartialChoiceCodesSuccessfullyComputed(dto, generationInput, representations, computedRepresentations);
        logChoiceCodesExponentiationProofSuccessfullyComputed(dto, generationInput, representations,
            computedRepresentations, powersAndProof.proof());
        return computedRepresentations;
    }

    /**
     * @param dto
     * @param generationInput
     */
    private void logErrorComputingPartialChoiceCodeGEN(
            final ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto,
            final ChoiceCodeGenerationInput generationInput) {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        secureLoggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder()
                .logEvent(ControlComponentsCommonsLogEvents.ERROR_COMPUTING_PARTIAL_CHOICE_CODE_GEN)
                .user(verificationCardId).objectId(verificationCardSetId).electionEvent(electionEventId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .additionalInfo(ControlComponentsCommonsLogConstants.ERR_DESC,
                    ControlComponentsCommonsLogEvents.ERROR_COMPUTING_PARTIAL_CHOICE_CODE_GEN.getInfo())
                .createLogInfo());
    }

    private Exponent deriveChoiceCodesExponent(ChoiceCodeGenerationInput generationInput, ElGamalPrivateKey privateKey,
            String verificationCardSetId) throws GeneralCryptoLibException {
        Exponent exponent = privateKey.getKeys().get(0);
        BigInteger q = privateKey.getGroup().getQ();
        Exponent derivedExponent;
        try {
            String seed = generationInput.getVerificationCardId();
            derivedExponent = keyDerivationService.deriveKey(exponent, seed, q);
            logChoiceCodesPrivateKeySuccessfullyComputed(generationInput, verificationCardSetId);
        } catch (GeneralCryptoLibException e) {
            logChoiceCodesPrivateKeyDerivationFailed(generationInput, verificationCardSetId);
            throw e;
        }
        return derivedExponent;
    }

    private Exponent deriveBallotCastingKeyExponent(ChoiceCodeGenerationInput generationInput,
            ElGamalPrivateKey privateKey, String verificationCardSetId) throws GeneralCryptoLibException {
        Exponent exponent = privateKey.getKeys().get(0);
        BigInteger q = privateKey.getGroup().getQ();
        Exponent derivedExponent;
        try {
            String seed = generationInput.getVerificationCardId() + CONFIRM_STRING_PADDING;
            derivedExponent = keyDerivationService.deriveKey(exponent, seed, q);
            logBallotCastingKeyExponentiationKeyDerivationSuccessfullyGenerated(generationInput, verificationCardSetId);
        } catch (GeneralCryptoLibException e) {
            logBallotCastingKeyExponentiationKeyDerivationFailed(generationInput, verificationCardSetId);
            throw e;
        }
        return derivedExponent;
    }

    private ZpGroupElement deriveKey(ZpSubgroup group, Exponent exponent) throws GeneralCryptoLibException {
        return group.getGenerator().exponentiate(exponent);
    }

    private ElGamalPrivateKey getGenerationPrivateKey(ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto)
            throws KeyManagementException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        return choiceCodesKeyRepository.getGenerationPrivateKey(electionEventId, verificationCardSetId);
    }

    private List<ElGamalComputationsValues> getRepresentations(ChoiceCodeGenerationInput generationInput)
            throws GeneralCryptoLibException {
        return codec.decodeList(generationInput.getEncryptedRepresentations());
    }

    private void logBallotCastingKeyExponentiationProofSuccessfullyComputed(
            ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto, ChoiceCodeGenerationInput generationInput,
            ElGamalComputationsValues encryptedBck, ElGamalComputationsValues computedBck, Proof proof)
            throws GeneralCryptoLibException, JsonProcessingException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        String bckProof = Base64.getEncoder().encodeToString(proof.toJson().getBytes(UTF_8));
        String encryptedCM = ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValues(encryptedBck));
        String exponentiatedEncryptedCM =
            ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValues(computedBck));

        secureLoggingWriter
            .log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        ControlComponentsCommonsLogEvents.PARTIAL_VOTE_CAST_CODE_PROOF_KNOWLEDGE_EXPONENT_COMPUTED)
                    .user(verificationCardId).objectId(verificationCardId).electionEvent(electionEventId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                        serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                    .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.VERIFICATION_CARD_SET_ID,
                        verificationCardSetId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.BALLOT_CASTING_KEY_PROOF, bckProof)
                    .additionalInfo(ControlComponentsCommonsLogConstants.ENCRYPTED_BALLOT_CASTING_KEY, encryptedCM)
                    .additionalInfo(ControlComponentsCommonsLogConstants.EXPONENTIATED_ENCRYPTED_BALLOT_CASTING_KEY,
                        exponentiatedEncryptedCM)
                    .createLogInfo());
    }

    private void logBallotCastingKeySuccessfullyComputed(ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto,
            ChoiceCodeGenerationInput generationInput, ElGamalComputationsValues encryptedBck,
            ElGamalComputationsValues computedBck) throws GeneralCryptoLibException, JsonProcessingException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        String hashBck = ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValues(encryptedBck));
        String hashComputedBck = ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValues(computedBck));

        secureLoggingWriter.log(Level.INFO,
            new LogContent.LogContentBuilder()
                .logEvent(ControlComponentsCommonsLogEvents.PARTIAL_VOTE_CAST_CODE_COMPUTED_GEN)
                .user(verificationCardId).objectId(verificationCardSetId).electionEvent(electionEventId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.HASH_ENCCM, hashBck)
                .additionalInfo(ControlComponentsCommonsLogConstants.HASH_COMPUTED_ENCCM, hashComputedBck)
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }

    private void logChoiceCodesExponentiationProofSuccessfullyComputed(
            ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto, ChoiceCodeGenerationInput generationInput,
            List<ElGamalComputationsValues> encryptedRepresentations,
            List<ElGamalComputationsValues> computedRepresentations, Proof proof)
            throws GeneralCryptoLibException, JsonProcessingException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        String primesProof = Base64.getEncoder().encodeToString(proof.toJson().getBytes(UTF_8));
        String encryptedPrimes =
            ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValuesList(encryptedRepresentations));
        String exponentiatedEncryptedPrimes =
            ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValuesList(computedRepresentations));

        secureLoggingWriter
            .log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ControlComponentsCommonsLogEvents.PARTIAL_CHOICE_CODE_PROOF_KNOWLEDGE_EXPONENT_COMPUTED)
                    .user(verificationCardId).objectId(verificationCardId).electionEvent(electionEventId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                        serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                    .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.VERIFICATION_CARD_SET_ID,
                        verificationCardSetId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.PRIMES_PROOF, primesProof)
                    .additionalInfo(ControlComponentsCommonsLogConstants.ENCRYPTED_PRIMES, encryptedPrimes)
                    .additionalInfo(ControlComponentsCommonsLogConstants.EXPONENTIATED_ENCRYPTED_PRIMES,
                        exponentiatedEncryptedPrimes)
                    .createLogInfo());
    }

    private void logPartialChoiceCodesSuccessfullyComputed(ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto,
            ChoiceCodeGenerationInput generationInput, List<ElGamalComputationsValues> encryptedRepresentations,
            List<ElGamalComputationsValues> computedRepresentations)
            throws GeneralCryptoLibException, JsonProcessingException {
        String electionEventId = dto.getPayload().getElectionEventId();
        String verificationCardSetId = dto.getPayload().getVerificationCardSetId();
        String verificationCardId = generationInput.getVerificationCardId();
        String encryptedRepresentationsHash =
            ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValuesList(encryptedRepresentations));
        String computedRepresentationsHash =
            ObjectMappers.toJson(getHashListForLogFromElGamalComputationsValuesList(computedRepresentations));

        secureLoggingWriter
            .log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ControlComponentsCommonsLogEvents.PARTIAL_CHOICE_CODE_COMPUTED_GEN)
                    .user(verificationCardId).objectId(verificationCardSetId).electionEvent(electionEventId)
                    .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                        serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                    .additionalInfo(ControlComponentsCommonsLogConstants.HASH_ENPCC, encryptedRepresentationsHash)
                    .additionalInfo(ControlComponentsCommonsLogConstants.HASH_COMPUTED_ENPCC,
                        computedRepresentationsHash)
                    .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                    .createLogInfo());
    }

    private void logChoiceCodesPrivateKeyDerivationFailed(ChoiceCodeGenerationInput generationInput,
            String verificationCardSetId) {
        String verificationCardId = generationInput.getVerificationCardId();
        secureLoggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder()
                .logEvent(ControlComponentsCommonsLogEvents.CHOICE_CODE_PRIVATE_KEY_DERIVED_GEN_FAILED)
                .objectId(verificationCardSetId).user(verificationCardId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.ERR_DESC,
                    "The derivation of the private key has failed")
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }

    private void logChoiceCodesPrivateKeySuccessfullyComputed(ChoiceCodeGenerationInput generationInput,
            String verificationCardSetId) {
        String verificationCardId = generationInput.getVerificationCardId();
        secureLoggingWriter.log(Level.INFO,
            new LogContent.LogContentBuilder().objectId(verificationCardSetId).user(verificationCardId)
                .logEvent(ControlComponentsCommonsLogEvents.CHOICE_CODE_PRIVATE_KEY_DERIVED_GEN)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }
    
    private void logBallotCastingKeyExponentiationKeyDerivationFailed(ChoiceCodeGenerationInput generationInput,
            String verificationCardSetId) {
        String verificationCardId = generationInput.getVerificationCardId();
        secureLoggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder()
                .logEvent(ControlComponentsCommonsLogEvents.BALLOT_CASTING_KEY_EXPONENTIATION_KEY_DERIVED_GEN_FAILED)
                .objectId(verificationCardSetId).user(verificationCardId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.ERR_DESC,
                    "The derivation of the private key has failed")
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }
    
    private void logBallotCastingKeyExponentiationKeyDerivationSuccessfullyGenerated(ChoiceCodeGenerationInput generationInput,
            String verificationCardSetId) {
        String verificationCardId = generationInput.getVerificationCardId();
        secureLoggingWriter.log(Level.INFO,
            new LogContent.LogContentBuilder().objectId(verificationCardSetId).user(verificationCardId)
                .logEvent(ControlComponentsCommonsLogEvents.BALLOT_CASTING_KEY_EXPONENTIATION_KEY_DERIVED_GEN)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }

    private void sendResponse(ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto,
            List<ChoiceCodeGenerationOutput> choiceCodeGenerationOutputList)
            throws MessagingException, GeneralCryptoLibException, KeyNotFoundException, KeyManagementException,
            PayloadSignatureException {
        String eeid = dto.getPayload().getElectionEventId();

        ChoiceCodeGenerationResPayload resPayload = new ChoiceCodeGenerationResPayload(dto.getPayload().getTenantId(),
            eeid, dto.getPayload().getVerificationCardSetId(), dto.getPayload().getChunkId());

        resPayload.setChoiceCodeGenerationOutputList(choiceCodeGenerationOutputList);
        try {
            resPayload.setSignature(payloadSigner.sign(resPayload, keyManager.getElectionSigningPrivateKey(eeid),
                keyManager.getElectionSigningCertificateChain(eeid)));
        } catch (PayloadSignatureException e) {
            // [SL GENPCSIGN-12]
            logErrorPartialCodeCannotBeSigned(resPayload);
            throw e;
        }
        logPartialCodesSigned(resPayload);

        UUID correlationId = dto.getCorrelationId();
        String requestId = dto.getRequestId();
        ChoiceCodeGenerationDTO<ChoiceCodeGenerationResPayload> result =
            new ChoiceCodeGenerationDTO<>(correlationId, requestId, resPayload);

        messagingService.send(computationOutputQueue, result);
    }

    /**
     * @param resPayload
     */
    private void logErrorPartialCodeCannotBeSigned(final ChoiceCodeGenerationResPayload resPayload) {
        String electionEventId = resPayload.getElectionEventId();
        String verificationCardSetId = resPayload.getVerificationCardSetId();
        secureLoggingWriter.log(Level.ERROR, new LogContent.LogContentBuilder().objectId(verificationCardSetId)
            .logEvent(ControlComponentsCommonsLogEvents.ERROR_PARTIAL_CODE_NOT_SIGNED).electionEvent(electionEventId)
            .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
            .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
            .additionalInfo(ControlComponentsCommonsLogConstants.ERR_DESC,
                ControlComponentsCommonsLogEvents.ERROR_PARTIAL_CODE_NOT_SIGNED.getInfo())
            .createLogInfo());
    }

    private void logPartialCodesSigned(ChoiceCodeGenerationResPayload data) {
        String electionEventId = data.getElectionEventId();
        String verificationCardSetId = data.getVerificationCardSetId();
        secureLoggingWriter.log(Level.INFO,
            new LogContent.LogContentBuilder().objectId(verificationCardSetId)
                .logEvent(ControlComponentsCommonsLogEvents.PARTIAL_CODE_GEN_SIGN).electionEvent(electionEventId)
                .additionalInfo(ControlComponentsCommonsLogConstants.REQUEST_ID,
                    serviceTransactionInfoProvider.getServiceTransactionInfo().getTrackId())
                .additionalInfo(ControlComponentsCommonsLogConstants.CONTROL_COMPONENT_ID, controlComponentId)
                .createLogInfo());
    }

    public List<String> getHashListForLogFromElGamalComputationsValues(
            ElGamalComputationsValues elGamalComputationsValues) throws GeneralCryptoLibException {

        List<ZpGroupElement> values = elGamalComputationsValues.getValues();
        List<String> valuesHashes = new ArrayList<>(values.size());
        for (ZpGroupElement value : values) {
            byte[] valueHash = primitivesService.getHash(value.getValue().toByteArray());
            valuesHashes.add(Base64.getEncoder().encodeToString(valueHash));
        }
        return valuesHashes;
    }

    public List<String> getHashListForLogFromElGamalComputationsValuesList(
            List<ElGamalComputationsValues> elGamalComputationsValuesList) throws GeneralCryptoLibException {

        List<String> valuesHashes = new ArrayList<>();
        for (ElGamalComputationsValues elGamalComputationsValues : elGamalComputationsValuesList) {
            valuesHashes.addAll(getHashListForLogFromElGamalComputationsValues(elGamalComputationsValues));
        }
        return valuesHashes;
    }

}
