/*
 * 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.sdm.application.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.codec.binary.Base64;
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 javax.json.JsonWriter;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
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.commons.beans.EncryptionParameters;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetId;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetIdImpl;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.sign.MetadataFileSigner;
import com.scytl.products.ov.commons.sign.SignatureMetadata;
import com.scytl.products.ov.commons.sign.beans.SignatureFieldsConstants;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.mixnet.webapp.mvc.MixDecValResult;
import com.scytl.products.ov.sdm.application.exception.SignatureException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;

/**
 * Service which will invoke the mixing operation.
 */
@Service
public class MixDecValService {

    private static final Logger LOG = LoggerFactory.getLogger(MixDecValService.class);

    private static final String JSON_FIELD_CONCURRENCY_LEVEL = "concurrencyLevel";

    private static final String JSON_FIELD_NUMBER_PARALLEL_BATCH = "numberOfParallelBatches";

    private static final String JSON_FIELD_NUM_OPTIONS = "numOptions";

    private static final String JSON_FIELD_MINIMUM_BATCH_SIZE = "minimumBatchSize";

    private static final String JSON_OBJECT_MIXER = "mixer";

    private static final String JSON_FIELD_MIX = "mix";

    private static final String JSON_OBJECT_INPUT = "input";

    private static final String JSON_FIELD_PUBLIC_KEY = "publicKey";

    private static final String JSON_FIELD_ENCRYPTED_BALLOTS = "encryptedBallots";

    private static final String JSON_FIELD_PLATFORM_ROOT = "platformRoot";

    private static final String JSON_FIELD_ENCRYPTION_PARAMETERS = "encryptionParameters";

    private static final String JSON_FIELD_MIXING_OUTPUT_DIRECTORY = "outputDirectory";

    private static final String JSON_OBJECT_VERIFIER = "verifier";

    private static final String JSON_FIELD_VERIFY = "verify";

    private static final String JSON_FIELD_VERIFIER_INPUT_DIRECTORY = "inputDirectory";

    private static final String JSON_FIELD_VERIFIER_OUTPUT_DIRECTORY = "outputDirectory";

    private static final String JSON_FIELD_BALLOT_DECRYPTION_KEY = "ballotDecryptionKey";

    private static final String JSON_OBJECT_VALIDATOR = "validator";

    private static final String MIXING_SERVICE_NAME = "mixing";

    /**
     * The name of the base directory where the mixing installation is located.
     */
    public static final String MIXING_BASE_DIR = "sdm/mixing";

    /**
     * The name of the command to execute the mixing.
     */
    public static final String MIXING_EXEC_COMMAND = "mixnet";

    private final BallotRepository ballotRepository;

    private final BallotBoxRepository ballotBoxRepository;

    private final ElectionEventRepository electionEventRepository;

    private final PathResolver pathResolver;

    private final ConfigurationEntityStatusService configurationEntityStatusService;

    private final MetadataFileSigner metadataFileSigner;
    
    private final PlatformRootCAService platformRootCAService;

    private final SecureLoggingWriter secureLogger;

    @Value("${tenantID}")
    private String tenantId;

    @Value("${MIXING_URL}")
    private String mixingURL;

    @Autowired
    public MixDecValService(BallotRepository ballotRepository, BallotBoxRepository ballotBoxRepository,
            ElectionEventRepository electionEventRepository, PathResolver pathResolver,
            ConfigurationEntityStatusService configurationEntityStatusService, MetadataFileSigner metadataFileSigner,
            PlatformRootCAService platformRootCAService, SecureLoggingWriter secureLogger) {
        this.ballotRepository = ballotRepository;
        this.ballotBoxRepository = ballotBoxRepository;
        this.electionEventRepository = electionEventRepository;
        this.pathResolver = pathResolver;
        this.configurationEntityStatusService = configurationEntityStatusService;
        this.metadataFileSigner = metadataFileSigner;        
        this.platformRootCAService = platformRootCAService;
        this.secureLogger = secureLogger;
    }

    /**
     * Performs the mixing, decryption and validation of a specific ballot box.
     *
     * @param electionEventId
     *            - identifier of the election event.
     * @param ballotBoxId
     *            - identifier of the ballot box.
     * @param signingKeyPEM
     *            The PEM representation of the key used to sign the ballot box.
     * @param ballotDecryptionKey
     *            the PEM representation of the key used to decrypt ballots.
     * @return True if the mixing is successfully executed.
     */
    public boolean mixDecryptValidate(String electionEventId, String ballotBoxId, String signingKeyPEM,
            String ballotDecryptionKey)
            throws IOException, GeneralCryptoLibException, ResourceNotFoundException, ApplicationException {

        JsonObject ballotBoxJsonObject = getBallotBoxJsonObject(ballotBoxId);

        checkBallotBoxStatus(ballotBoxJsonObject);

        Path ballotBoxPath = getBallotBoxPath(ballotBoxJsonObject, ballotBoxId, electionEventId);

        /* Write the encryption params input in txt format */
        writeEncryptionParamsInputIntoTxtFile(ballotBoxPath, electionEventId);

        /* Write the mixing application config json */

        // Asumming a single vote set by now
        int voteSetIdIndex = 0;

        // Retrieve and check encrypted ballots paths
        List<Path> encryptedBallotsPaths =
            getEncryptedBallotsPaths(ballotBoxPath, tenantId, electionEventId, ballotBoxId, voteSetIdIndex);

        String encryptedBallotsPathsString = ObjectMappers.toJson(encryptedBallotsPaths);

        // Create mixing config json and save it to disk.
        String mixingConfigJsonStr = createMixingConfigJson(ballotBoxPath, encryptedBallotsPathsString, ballotBoxId,
            electionEventId, signingKeyPEM, ballotDecryptionKey);

        // Run the mix+dec+val process and obtain the list of paths it has
        // created.
        List<Path> mixingDirectories = doMixDecVal(mixingConfigJsonStr, ballotBoxId, electionEventId);
        if (mixingDirectories != null) {

            // Improvement: The signature of the mixing and decryption outputs
            // would be better done within the mixing and decryption crypto
            // services scope
            signMixingProofs(signingKeyPEM, electionEventId, ballotBoxId, mixingDirectories);

            configurationEntityStatusService.update(Status.DECRYPTED.name(), ballotBoxId, ballotBoxRepository);

            return true;
        } else {
            return false;
        }
    }

    private PrivateKey getSigningKeyFromPEM(String signingKeyPEM) {
        try {
            return PemUtils.privateKeyFromPem(signingKeyPEM);
        } catch (GeneralCryptoLibException e) {
            throw new IllegalArgumentException("Could not recover signing private key from PEM", e);
        }
    }

    /**
     * It search by ID inside Ballot Box repository and returns a JSON object.
     */
    private JsonObject getBallotBoxJsonObject(String ballotBoxId) throws ResourceNotFoundException {
        JsonObject ballotBox = JsonUtils.getJsonObject(ballotBoxRepository.find(ballotBoxId));

        if (ballotBox == null || ballotBox.size() == 0) {
            throw new ResourceNotFoundException("Ballot box entity does not exist for id: " + ballotBoxId);
        }
        return ballotBox;
    }

    /**
     * Checks the expected Status from a Ballot Box JSON object.
     */
    private void checkBallotBoxStatus(JsonObject ballotBox) throws ApplicationException {

        try {
            String status = ballotBox.getString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
            if (!Status.valueOf(status).equals(Status.BB_DOWNLOADED)) {
                throw new ApplicationException("Ballot box entity status not ready");
            }
        } catch (IllegalArgumentException e) {
            throw new ApplicationException("Ballot box entity status not ready", e);
        }
    }

    private void signMixingProofs(String signingKeyPEM, String electionEventId, String ballotBoxId,
            List<Path> mixingDirectories) {

        PrivateKey signingKey = getSigningKeyFromPEM(signingKeyPEM);
        LinkedHashMap<String, String> fields = getMetadataFields(electionEventId, ballotBoxId);

        mixingDirectories.forEach(filePath -> {

            try {

                final Path commitmentParametersFilePath = pathResolver.resolve(filePath.toString(),
                    ConfigConstants.CONFIG_FILE_NAME_COMMITMENT_PARAMS + ConfigConstants.JSON);
                final Path encryptedBallotsFilePath =
                    pathResolver.resolve(filePath.toString(), ConfigConstants.CONFIG_FILE_NAME_ENCRYPTED_BALLOTS);
                final Path proofsFilePath = pathResolver.resolve(filePath.toString(),
                    ConfigConstants.CONFIG_FILE_NAME_PROOFS + ConfigConstants.JSON);
                final Path reencryptedBallotsFilePath = pathResolver.resolve(filePath.toString(),
                    ConfigConstants.CONFIG_FILE_NAME_REENCRYPTED_BALLOT_BOX + ConfigConstants.CSV);
                final Path votesWithProofFilePath = pathResolver.resolve(filePath.toString(),
                    ConfigConstants.CONFIG_FILE_NAME_VOTES_WITH_PROOF + ConfigConstants.CSV);
                final Path votesFilePath = pathResolver.resolve(filePath.toString(),
                    ConfigConstants.CONFIG_FILE_NAME_VOTES + ConfigConstants.CSV);

                signMixingProofsWithMetadata(signingKey, fields, commitmentParametersFilePath, encryptedBallotsFilePath,
                    proofsFilePath, reencryptedBallotsFilePath, votesWithProofFilePath, votesFilePath);

            } catch (IOException | GeneralCryptoLibException e) {
                LOG.error("Error occurred during the signature of mixing proofs");
                throw new SignatureException("Error during the signature of mixing proofs", e);
            }
        });
    }

    private void signMixingProofsWithMetadata(PrivateKey signingKey, LinkedHashMap<String, String> fields,
            Path... paths) throws IOException, GeneralCryptoLibException {

        String proofsFileName = ConfigConstants.CONFIG_FILE_NAME_PROOFS + ConfigConstants.JSON;

        for (Path filePath : paths) {

            // when there is only one vote, proofs.json file is not generated
            // nor signed
            if (!filePath.toFile().exists() && proofsFileName.equals(filePath.getFileName().toString())) {
                continue;
            }

            Path metadataFilePath = filePath.resolveSibling(filePath.getFileName() + ".metadata");

            try (InputStream fileIn = Files.newInputStream(filePath);
                    OutputStream os = Files.newOutputStream(metadataFilePath, StandardOpenOption.CREATE);
            		JsonWriter jsonWriter = Json.createWriter(os)) {
                final SignatureMetadata signature = metadataFileSigner.createSignature(signingKey, fileIn, fields);
                jsonWriter.writeObject(signature.toJsonObject());
            }
        }
    }

    private LinkedHashMap<String, String> getMetadataFields(String electionEventId, String ballotBoxId) {

        final LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put(SignatureFieldsConstants.SIG_FIELD_TENANTID, tenantId);
        fields.put(SignatureFieldsConstants.SIG_FIELD_EEVENTID, electionEventId);
        fields.put(SignatureFieldsConstants.SIG_FIELD_BBOXID, ballotBoxId);
        fields.put(SignatureFieldsConstants.SIG_FIELD_TIMESTAMP, DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
        fields.put(SignatureFieldsConstants.SIG_FIELD_COMPONENT, MIXING_SERVICE_NAME);

        return fields;
    }

    private JsonObject readBallotBoxJsonObject(Path ballotBoxPath) throws IOException {
        return JsonUtils.getJsonObject(new String(
            Files.readAllBytes(
                pathResolver.resolve(ballotBoxPath.toString(), ConfigConstants.CONFIG_FILE_NAME_BALLOT_BOX_JSON)),
            StandardCharsets.UTF_8));
    }

    private JsonObject readElectoralAuthorityJsonObject(Path electoralAuthorityPath) throws IOException {
        return JsonUtils
            .getJsonObject(new String(Files.readAllBytes(pathResolver.resolve(electoralAuthorityPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_DECRYPTION_KEY_JSON)), StandardCharsets.UTF_8));
    }

    /**
     * Writes the encryption params input in txt format.
     */
    private void writeEncryptionParamsInputIntoTxtFile(Path ballotBoxPath, String electionEventId)
            throws IOException, GeneralCryptoLibException {

        JsonObject ballotBoxObject = readBallotBoxJsonObject(ballotBoxPath);

        String electoralAuthorityId = ballotBoxObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORALAUTHORITYID);

        Path electoralAuthorityPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTORAL_AUTHORITY,
            electoralAuthorityId);

        JsonObject electoralAuthorityObject = readElectoralAuthorityJsonObject(electoralAuthorityPath);

        String publicKeyString = electoralAuthorityObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_PUBLIC_KEY);

        /* Write the encryption params input in txt format */
        JsonObject encryptionParameters =
            ballotBoxObject.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ENCRYPTION_PARAMS);

        String pAttribute = encryptionParameters.getString(JsonConstants.JSON_ATTRIBUTE_NAME_P);
        String qAttribute = encryptionParameters.getString(JsonConstants.JSON_ATTRIBUTE_NAME_Q);
        String gAttribute = encryptionParameters.getString(JsonConstants.JSON_ATTRIBUTE_NAME_G);
        String encryptionParametersString =
            new ZpSubgroup(new BigInteger(gAttribute), new BigInteger(pAttribute), new BigInteger(qAttribute))
                .toJson();

        Files.write(
            pathResolver.resolve(ballotBoxPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_ENCRYPTED_PARAMS + ConfigConstants.JSON),
            encryptionParametersString.getBytes(StandardCharsets.UTF_8));

        Files.write(
            pathResolver.resolve(ballotBoxPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_PUBLIC_KEY + ConfigConstants.JSON),
            Base64.decodeBase64(publicKeyString));
    }

    private Path getBallotBoxPath(JsonObject ballotBox, String ballotBoxId, String electionEventId) {
        String ballotId = ballotBox.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT)
            .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

        return pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION,
            ConfigConstants.CONFIG_DIR_NAME_BALLOTS, ballotId, ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES,
            ballotBoxId);
    }

    public Path getBallotBoxPath(String ballotId, String ballotBoxId, String electionEventId) {
        return pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION,
            ConfigConstants.CONFIG_DIR_NAME_BALLOTS, ballotId, ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES,
            ballotBoxId);
    }

    /**
     * Create Mixing configuration JSON file, return the JSON as String.
     */
    private String createMixingConfigJson(Path ballotBoxPath, String encryptedBallotsPathsString, String ballotBoxId,
            String electionEventId, String signingKeyPEM, String ballotDecryptionKey) throws ApplicationException {
        JsonObjectBuilder configJOB = Json.createObjectBuilder();
        configJOB.add(JSON_FIELD_CONCURRENCY_LEVEL, 1);
        configJOB.add(JSON_FIELD_NUMBER_PARALLEL_BATCH, 16);
        configJOB.add(JSON_FIELD_NUM_OPTIONS, 1);
        configJOB.add(JSON_FIELD_MINIMUM_BATCH_SIZE, 4096);

        configJOB.add(JSON_OBJECT_MIXER, getMixerJOB(ballotBoxPath, encryptedBallotsPathsString, ballotDecryptionKey));
        configJOB.add(JSON_OBJECT_VERIFIER, getVerifierJOB(ballotBoxPath));
        configJOB.add(JSON_OBJECT_VALIDATOR, getValidatorJOB(ballotBoxId, electionEventId, signingKeyPEM));

        // Return the JSON string
        return configJOB.build().toString();
    }

    /**
     * Creates the mixing configuration.
     * 
     * @return a JSON object builder with the mixing configuration
     * @throws ApplicationException
     */
    private JsonObjectBuilder getMixerJOB(Path ballotBoxPath, String encryptedBallotsPathsString,
            String ballotDecryptionKey) throws ApplicationException {
        JsonObjectBuilder inputJOB = Json.createObjectBuilder();
        inputJOB.add(JSON_FIELD_PUBLIC_KEY,
            pathResolver.resolve(ballotBoxPath.toString(), ConfigConstants.CONFIG_FILE_NAME_PUBLIC_KEY).toString());
        inputJOB.add(JSON_FIELD_ENCRYPTED_BALLOTS, encryptedBallotsPathsString);
        try {
            X509Certificate platformRootCertificate = platformRootCAService.load();
            inputJOB.add(JSON_FIELD_PLATFORM_ROOT, PemUtils.certificateToPem(platformRootCertificate));
        } catch (GeneralCryptoLibException | CertificateManagementException e) {
            throw new ApplicationException("Could not retrieve platform root certificate for mixing config", e);
        }
        inputJOB.add(JSON_FIELD_ENCRYPTION_PARAMETERS, pathResolver
            .resolve(ballotBoxPath.toString(), ConfigConstants.CONFIG_FILE_NAME_ENCRYPTED_PARAMS).toString());
        inputJOB.add(JSON_FIELD_BALLOT_DECRYPTION_KEY, ballotDecryptionKey);

        JsonObjectBuilder mixerJOB = Json.createObjectBuilder();
        mixerJOB.add(JSON_FIELD_MIX, true);
        mixerJOB.add(JSON_OBJECT_INPUT, inputJOB);
        mixerJOB.add(JSON_FIELD_MIXING_OUTPUT_DIRECTORY, ballotBoxPath.toString());
        return mixerJOB;
    }

    /**
     * Creates the verifier configuration
     *
     * @return a JSON object builder with the verifier configuration
     */
    private JsonObjectBuilder getVerifierJOB(Path ballotBoxPath) {
        JsonObjectBuilder verifierJOB = Json.createObjectBuilder();
        verifierJOB.add(JSON_FIELD_VERIFY, false);
        verifierJOB.add(JSON_FIELD_VERIFIER_INPUT_DIRECTORY, ballotBoxPath.toString());
        verifierJOB.add(JSON_FIELD_VERIFIER_OUTPUT_DIRECTORY, ballotBoxPath.toString());
        return verifierJOB;
    }

    /**
     * Creates the validator configuration
     *
     * @return a JSON object builder with the validator configuration
     */
    private JsonObjectBuilder getValidatorJOB(String ballotBoxId, String electionEventId, String signingKeyPEM)
            throws ApplicationException {
        // find the ballot box
        String ballotBoxJson = ballotBoxRepository.list(getBallotBoxLocator(ballotBoxId, electionEventId));

        JsonArray ballotBoxJsonArray =
            JsonUtils.getJsonObject(ballotBoxJson).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

        if (ballotBoxJsonArray.isEmpty()) {
            throw new IllegalArgumentException(
                String.format("No ballot boxes found for ballot box ID %1$s and election event ID %2$s", ballotBoxId,
                    electionEventId));
        }

        JsonObject ballotBoxJsonObject = ballotBoxJsonArray.getJsonObject(0);
        checkBallotBoxStatus(ballotBoxJsonObject);

        String ballotId = ballotBoxJsonObject.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT)
            .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

        Path ballotBoxPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR).resolve(electionEventId)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_BALLOTS).resolve(ballotId)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES).resolve(ballotBoxId);

        String electionEventString = electionEventRepository.find(electionEventId);
        JsonObject electionEvent = JsonUtils.getJsonObject(electionEventString);
        String writeInAlphabetBase64 = electionEvent.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_SETTINGS)
            .getString(JsonConstants.JSON_PARAM_NAME_WRITE_IN_ALPHABET);

        JsonObjectBuilder validatorConfig = Json.createObjectBuilder();

        validatorConfig.add("ballotBoxId", ballotBoxId);
        validatorConfig.add("tenantId", tenantId);
        validatorConfig.add("ballot", JsonUtils.getJsonObject(getBallotJson(ballotId)));
        validatorConfig.add("elGamalEncryptionParametersP", getEncryptionParametersP(ballotBoxPath));
        validatorConfig.add("signingKey", signingKeyPEM);
        validatorConfig.add("writeInAlphabet", writeInAlphabetBase64);
        validatorConfig.add("outputPath", ballotBoxPath.toString());

        return validatorConfig;
    }

    /**
     * Get a ballot's JSON string representation.
     *
     * @param ballotId
     *            the ballot to fetch.
     * @return the ballot's JSON as a string
     */
    private String getBallotJson(String ballotId) {
        return ballotRepository.find(ballotId);
    }

    /**
     * Retrieve and check encrypted ballots path
     */
    private List<Path> getEncryptedBallotsPaths(Path ballotBoxPath, String tenantId, String electionEventId,
            String ballotBoxId, int voteSetIdIndex) throws ResourceNotFoundException {

        VoteSetId voteSetId =
            new VoteSetIdImpl(new BallotBoxIdImpl(tenantId, electionEventId, ballotBoxId), voteSetIdIndex);

        String filter = voteSetId.toString();

        List<Path> encryptedBallotsPaths = new ArrayList<>();
        try {
            Files.walk(ballotBoxPath, 1).filter(f -> f.getFileName().toString().startsWith(filter))
                .forEach(encryptedBallotsPaths::add);
            if (encryptedBallotsPaths.isEmpty()) {
                throw new ResourceNotFoundException("No encrypted ballots paths found");
            }
        } catch (IOException e) {
            throw new ResourceNotFoundException("An error occured retrieving the encrypted ballots paths", e);
        }

        return encryptedBallotsPaths;
    }

    /**
     * Starts the MixDecVal end-point in the cryptoservices and retrieves the
     * result. This method returns null if the process has failed, to keep with
     * the previous version of it, which returned true or false (instead of
     * result/exception).
     * 
     * @param mixingConfigJson
     *            the configuration to send to the mixing web app
     * @param ballotBoxId
     *            requires for logging in the case of error
     * @param electionEventId
     *            requires for logging in the case of error
     * @return the list of paths the mixing and decryption has produced, null if
     *         the process has failed.
     */
    private List<Path> doMixDecVal(String mixingConfigJson, final String ballotBoxId, final String electionEventId) {
        List<Path> result = null;

        try {
            // Run the mixing+decryption+validation process.
            MixDecValResult mixDecValResult = requestMixDecValResult(mixingConfigJson);
            result = mixDecValResult.getMixingDirectories();
        } catch (ProcessingException e) {
            LOG.error("Error performing post request", e);

            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.BALLOT_BOX_MIXING_FAILED)
                    .objectId(ballotBoxId).electionEvent(electionEventId)
                    .additionalInfo("err_desc", "Error performing post request " + e.getMessage()).createLogInfo());
        }

        return result;
    }

    /**
     * Send the data to be mixed, decrypted and validated to the mixdecval web
     * application.
     * 
     * @param mixingConfigJson
     *            the data to perform the mix-dec-val process
     * @return the result of the mix-dec-val process.
     */
    public MixDecValResult requestMixDecValResult(String mixingConfigJson) {
        // Run the mixing+decryption+validation process.
        WebTarget mixingWebapp = ClientBuilder.newClient().target(mixingURL);
        Response response = mixingWebapp.request().post(Entity.entity(mixingConfigJson, MediaType.APPLICATION_JSON));

        // If the service returned correctly, parse the response.
        MixDecValResult mixDecValResult = response.readEntity(MixDecValResult.class);
        if (response.getStatus() == Response.Status.OK.getStatusCode()) {
            return mixDecValResult;
        } else {
            throw new ProcessingException("The mix/dec/val process failed", mixDecValResult.getError());
        }
    }

    /**
     * Builds a map with the keys required to locate a ballot box in the
     * repository.
     *
     * @param ballotBoxId
     *            a reference to the ballot box
     * @param electionEventId
     *            a reference to the election event
     * @return a map with the ballot box and election event field names and
     *         values
     */
    private static Map<String, Object> getBallotBoxLocator(String ballotBoxId, String electionEventId) {
        Map<String, Object> attributeValueMap = new HashMap<>();
        attributeValueMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotBoxId);
        attributeValueMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEventId);
        return attributeValueMap;
    }

    /**
     * Loads the encryption parameters from a ballot box and retrieves the 'P'
     * element.
     *
     * @param ballotBoxPath
     *            the path where the ballot box can be found.
     * @return the encryption parameters' P
     */
    private String getEncryptionParametersP(Path ballotBoxPath) throws ApplicationException {

        JsonObject ballotBoxFromFileAsObject;
        try {
            ballotBoxFromFileAsObject = JsonUtils.getJsonObject(
                new String(Files.readAllBytes(ballotBoxPath.resolve(ConfigConstants.CONFIG_FILE_NAME_BALLOT_BOX_JSON)),
                    StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new ApplicationException(
                String.format("Failed to load ballot box JSON file from %s", ballotBoxPath.toString()), e);
        }

        String encryptionParametersAsString =
            ballotBoxFromFileAsObject.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ENCRYPTION_PARAMS).toString();

        EncryptionParameters encryptionParameters;

        try {
            encryptionParameters = ObjectMappers.fromJson(encryptionParametersAsString, EncryptionParameters.class);
        } catch (IOException e) {
            throw new ApplicationException(
                "Exception while trying to construct encryption parameters object from string", e);
        }

        return encryptionParameters.getP();
    }

}
