/*
 * 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 static java.nio.file.Files.exists;
import static java.util.Collections.singletonList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalEncrypter;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.choices.codes.BallotCastingKeyGenerator;
import com.scytl.products.ov.commons.beans.VerificationCardSetData;
import com.scytl.products.ov.commons.beans.VoteVerificationContextData;
import com.scytl.products.ov.commons.beans.domain.model.compute.VerificationCardIdToNodesContributions;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.verify.CSVVerifier;
import com.scytl.products.ov.commons.verify.JSONVerifier;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.sdm.application.service.requestBeans.SignRequest;
import com.scytl.products.ov.sdm.domain.common.SignedObject;
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.generator.DataGeneratorResponse;
import com.scytl.products.ov.sdm.domain.model.status.InvalidStatusTransitionException;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.model.votingcardset.VotingCardSetRepository;
import com.scytl.products.ov.sdm.domain.service.BallotBoxDataGeneratorService;
import com.scytl.products.ov.sdm.domain.service.BallotDataGeneratorService;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.domain.service.EncryptionParametersDataGeneratorService;
import com.scytl.products.ov.sdm.domain.service.VerificationCardDataGeneratorService;
import com.scytl.products.ov.sdm.domain.service.VotingCardSetDataGeneratorService;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.cc.ChoiceCodeGenerationRequestPayloadRepository;
import com.scytl.products.ov.sdm.infrastructure.cc.PayloadStorageException;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import com.scytl.products.ov.utils.ConfigObjectMapper;

import mockit.Deencapsulation;

/**
 * JUnit for the class {@link VotingCardSetService}.
 */
@RunWith(MockitoJUnitRunner.class)
public class VotingCardSetServiceTest extends BaseVotingCardSetServiceTest {

    private static final String ELECTION_EVENT_ID = "989";

    private static BigInteger P = new BigInteger(
        "16370518994319586760319791526293535327576438646782139419846004180837103527129035954742043590609421369665944746587885814920851694546456891767644945459124422553763416586515339978014154452159687109161090635367600349264934924141746082060353483306855352192358732451955232000593777554431798981574529854314651092086488426390776811367125009551346089319315111509277347117467107914073639456805159094562593954195960531136052208019343392906816001017488051366518122404819967204601427304267380238263913892658950281593755894747339126531018026798982785331079065126375455293409065540731646939808640273393855256230820509217411510058759");

    private static BigInteger Q = new BigInteger(
        "8185259497159793380159895763146767663788219323391069709923002090418551763564517977371021795304710684832972373293942907460425847273228445883822472729562211276881708293257669989007077226079843554580545317683800174632467462070873041030176741653427676096179366225977616000296888777215899490787264927157325546043244213195388405683562504775673044659657555754638673558733553957036819728402579547281296977097980265568026104009671696453408000508744025683259061202409983602300713652133690119131956946329475140796877947373669563265509013399491392665539532563187727646704532770365823469904320136696927628115410254608705755029379");

    private static final String BALLOT_JSON =
        "{\"id\":\"1c18f7d21dce4ac1aa1f025599bf2cfd\",\"defaultTitle\":\"ballot_Election-1\",\"defaultDescription\":\"ballot_Election-1\",\"alias\":\"ballot_1\",\"electionEvent\":{\"id\":\"a3d790fd1ac543f9b0a05ca79a20c9e2\"},\"contests\":[{\"id\":\"31d17941cacb4ea7b0dd45cd5bbc1074\",\"defaultTitle\":\"Regierungsratswahl\",\"alias\":\"Election-1\",\"defaultDescription\":\"Regierungsratswahl\",\"electionEvent\":{\"id\":\"a3d790fd1ac543f9b0a05ca79a20c9e2\"},\"template\":\"listsAndCandidates\",\"fullBlank\":\"true\",\"options\":[{\"id\":\"f4206470b5194e0086e548b1cbf532b5\",\"representation\":\"2\",\"attributes\":[\"47383e930b494529ae2cb96bc4e27307\"]},{\"id\":\"5d97df0d0c554c6fb9cd0a06c6296939\",\"representation\":\"5\",\"attributes\":[\"47383e930b494529ae2cb96bc4e27307\"]},{\"id\":\"d67ebbcff9ce40b989a7592c5f1aea9d\",\"representation\":\"13\",\"attributes\":[\"47383e930b494529ae2cb96bc4e27307\"]}],\"attributes\":[{\"id\":\"1a1c3be008be42ab920e740b5308cc24\",\"alias\":\"BLANK_0f2fd4984d2f4d88ae11ba6633efb57f\",\"correctness\":\"false\",\"related\":[\"ba7ca9c0d9594b409c259ece6ed0a16b\"]},{\"id\":\"ba7ca9c0d9594b409c259ece6ed0a16b\",\"alias\":\"lists\",\"correctness\":\"true\",\"related\":[]},{\"id\":\"47383e930b494529ae2cb96bc4e27307\",\"alias\":\"BLANK_b471aa814ed34ad5b93d4bde9ba98813\",\"correctness\":\"false\",\"related\":[\"7256d97c13cf4240ba7b647d78b150ee\"]},{\"id\":\"7746bfbce4594cd0a68607d08a9cd0a3\",\"alias\":\"WRITE_IN_0f2fd4984d2f4d88ae11ba6633efb57f\",\"correctness\":\"false\",\"related\":[\"ba7ca9c0d9594b409c259ece6ed0a16b\"]},{\"id\":\"d75a1f67671e4806be90797a6ea51258\",\"alias\":\"WRITE_IN_b471aa814ed34ad5b93d4bde9ba98813\",\"correctness\":\"false\",\"related\":[\"7256d97c13cf4240ba7b647d78b150ee\"]},{\"id\":\"7256d97c13cf4240ba7b647d78b150ee\",\"alias\":\"candidates\",\"correctness\":\"true\",\"related\":[]},{\"id\":\"862f4043e9cc492b88f7455987191406\",\"alias\":\"1-EVP\",\"correctness\":\"false\",\"related\":[\"ba7ca9c0d9594b409c259ece6ed0a16b\"]},{\"id\":\"d2c4025787cd497ea35d0e4d2605a959\",\"alias\":\"11-CSP\",\"correctness\":\"false\",\"related\":[\"ba7ca9c0d9594b409c259ece6ed0a16b\"]},{\"id\":\"87816135634e4e31ba42dcfcc50ee3df\",\"alias\":\"681ba77a-62ff-3449-9e3c-22777629faee\",\"correctness\":\"false\",\"related\":[\"862f4043e9cc492b88f7455987191406\",\"7256d97c13cf4240ba7b647d78b150ee\"]},{\"id\":\"4618b676d7a04f5dad83b39331c7ef0a\",\"alias\":\"15179821-f0ab-3cc6-b8d6-1f51e69f6819\",\"correctness\":\"false\",\"related\":[\"d2c4025787cd497ea35d0e4d2605a959\",\"7256d97c13cf4240ba7b647d78b150ee\"]}],\"questions\":[{\"id\":\"b471aa814ed34ad5b93d4bde9ba98813\",\"max\":\"10\",\"min\":\"1\",\"accumulation\":\"1\",\"writeIn\":\"true\",\"blankAttribute\":\"47383e930b494529ae2cb96bc4e27307\",\"writeInAttribute\":\"d75a1f67671e4806be90797a6ea51258\",\"attribute\":\"7256d97c13cf4240ba7b647d78b150ee\",\"fusions\":[]},{\"id\":\"0f2fd4984d2f4d88ae11ba6633efb57f\",\"max\":\"1\",\"min\":\"1\",\"accumulation\":\"1\",\"writeIn\":\"true\",\"blankAttribute\":\"1a1c3be008be42ab920e740b5308cc24\",\"writeInAttribute\":\"7746bfbce4594cd0a68607d08a9cd0a3\",\"attribute\":\"ba7ca9c0d9594b409c259ece6ed0a16b\",\"fusions\":[]}],\"encryptedCorrectnessRule\":\"function evaluateCorrectness(selection, callbackFunction){return true;}\",\"decryptedCorrectnessRule\":\"function evaluateCorrectness(selection, callbackFunction){return true;}\"}],\"status\":\"SIGNED\",\"details\":\"Ready to Mix\",\"synchronized\":\"false\",\"ballotBoxes\":\"FR-CH-1|FR-MU-9999\",\"signedObject\":\"fakesignature\"}";

    private static final String ELECTION_EVENT_JSON = "{\"id\":\"" + ELECTION_EVENT_ID
        + "\",\"defaultTitle\":\"JAVI EE 10vcs\",\"defaultDescription\":\"Javi Election with 10votincards\",\"alias\":\"JAVI EE 10\",\"dateFrom\":\"2015-01-01T01:01:00Z\",\"dateTo\":\"2015-02-01T01:01:00Z\",\"administrationAuthority\":{\"id\":\"60f3672954f74ac689a333ccd5bb6704\"},\"settings\":{\"electionEvent\":{\"id\":\""
        + ELECTION_EVENT_ID + "\"},\"encryptionParameters\":{\"p\":\"" + P + "\",\"q\":\"" + Q
        + "\",\"g\":\"2\"},\"certificatesValidityPeriod\":1,\"challengeLength\":16,\"challengeResponseExpirationTime\":2000,\"authTokenExpirationTime\":16000,\"numberVotesPerVotingCard\":1,\"numberVotesPerAuthToken\":1,\"maximumNumberOfAttempts\":5},\"status\":\"READY\",\"synchronized\":\"false\"}";

    private static final Logger LOGGER = LoggerFactory.getLogger(VotingCardSetServiceTest.class);

    private static final String VERIFICATION_CARD_SET_ID = "9a0";

    private static final String VERIFICATION_CARD_ID = "verificationcardid";

    private static final String COMPUTED_VALUES = "{\"100003\":{\"value\":153099029,\"p\":163705,\"q\":81852594971}";

    private static final String PRECOMPUTED_VALUES_PATH = "computeTest";

    private static final String ONLINE_PATH = "ONLINE";

    private static final String VOTE_VERIFICATION_FOLDER = "voteVerification";

    private Path tempDirectory;

    private Path sourceTestFilesPath;

    private final String adapterSignTenantCertName = "tenant_100.pem";

    private final String adapterSignRootCertName = "ScytlRootCA.pem";
    
    @Mock
    protected VotingCardSetRepository votingCardSetRepositoryMock;

    @Mock
    private EncryptionParametersDataGeneratorService encryptionParametersDataGeneratorServiceMock;

    @Mock
    private PathResolver pathResolver;

    @Mock
    private ObjectMapper objectMapperMock;

    @Mock
    private BallotBoxRepository ballotBoxRepository;

    @Mock
    private BallotRepository ballotRepository;

    @Mock
    private ElectionEventRepository electionEventRepository;

    @Mock
    private BallotDataGeneratorService ballotDataGeneratorServiceMock;

    @Mock
    private BallotBoxDataGeneratorService ballotBoxDataGeneratorServiceMock;

    @Mock
    private ConfigurationEntityStatusService configurationEntityStatusServiceMock;

    @Mock
    private VotingCardSetDataGeneratorService votingCardSetDataGeneratorServiceMock;

    @Mock
    private VerificationCardDataGeneratorService verificationCardDataGeneratorServiceMock;

    @Spy
    private ObjectMapper objectMapper;

    @Mock
    private VotingCardSetChoiceCodesService votingCardSetChoiceCodesServiceMock;

    @Mock
    private BallotCastingKeyGenerator ballotCastingKeyGenerator;
    
    @Mock
    private CryptoAPIElGamalEncrypter encrypter;
    
    @Mock
    private ElGamalServiceAPI elGamalService;

    @Mock
    private ElectionEventService electionEventService;
    
    @Mock
    private ElGamalComputationsValuesCodec codec;
    
    @Mock
    private SecureLoggingWriter secureLogger;
    
    @Mock
    private ChoiceCodeGenerationRequestPayloadRepository choiceCodeGenerationRequestPayloadRepository; 

    @Mock IdleStatusService idleStatusService;

    @InjectMocks
    private final VotingCardSetService sut = new VotingCardSetService();

    @BeforeClass
    public static void setUp() {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Before
    public void beforeEachTest() throws ResourceNotFoundException {

        SignatureService signatureService = new SignatureService();

        Deencapsulation.setField(sut, "signatureService", signatureService);

        ExtendedAuthenticationService extendedAuthenticationService = new ExtendedAuthenticationService();

        Deencapsulation.setField(extendedAuthenticationService, "signatureService", signatureService);

        Deencapsulation.setField(extendedAuthenticationService, "pathResolver", pathResolver);

        Deencapsulation.setField(sut, "extendedAuthenticationService", extendedAuthenticationService);

        Deencapsulation.setField(sut, "pathResolver", pathResolver);

        Deencapsulation.setField(sut, "secureLogger", secureLogger);
        
        Deencapsulation.setField(sut, "choiceCodeGenerationRequestPayloadRepository", choiceCodeGenerationRequestPayloadRepository);
        
        Deencapsulation.setField(sut, "idleStatusService", idleStatusService);

        Map<String, Object> attributeValueMap = new HashMap<>();
        attributeValueMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, ELECTION_EVENT_ID);
        attributeValueMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, VOTING_CARD_SET_ID);

        when(votingCardSetRepositoryMock.list(attributeValueMap)).thenReturn(listVotingCardSets("VCS_DOWNLOADED"));
        
        setStatusForVotingCardSetFromRepository("VCS_DOWNLOADED", votingCardSetRepositoryMock);
    }

    @Test
    public void sign()
            throws IOException, GeneralCryptoLibException, ResourceNotFoundException, InvalidStatusTransitionException {

        when(votingCardSetRepositoryMock.list(any())).thenReturn(listVotingCardSets(Status.GENERATED.name()));
        when(pathResolver.resolve(any())).thenReturn(Paths.get("src/test/resources/votingcardsetservice/"));
        
        setStatusForVotingCardSetFromRepository("GENERATED", votingCardSetRepositoryMock);

        assertThat(sut.sign(ELECTION_EVENT_ID, VOTING_CARD_SET_ID, SigningTestData.PRIVATE_KEY_PEM), is(true));

        CSVVerifier csvVerifier = new CSVVerifier();
        PublicKey publicKey = PemUtils.publicKeyFromPem(SigningTestData.PUBLIC_KEY_PEM);
        Path outputPath = Paths.get("src/test/resources/votingcardsetservice/989/ONLINE/");
        verifyVoterMaterialCSVs(csvVerifier, publicKey, outputPath);
        verifyVoteVerificationCSVs(csvVerifier, publicKey, outputPath);
        verifyVoteVerificationJSONs(publicKey, outputPath);
        verifyExtendedAuth(csvVerifier, publicKey, outputPath);
    }

    @Test
    public void sign_returns_false_when_the_requested_set_is_already_signed()
            throws IOException, GeneralCryptoLibException, ResourceNotFoundException, InvalidStatusTransitionException {

        String electionEventId = "989";
        String votingCardSetId = "355";

        String votingCardSet = "{\"result\": [{  \n" + "         \"id\":\"355\",\n"
            + "         \"defaultTitle\":\"Default Voting Card Set Title\",\n"
            + "         \"defaultDescription\":\"Default Voting Card Set Description\",\n"
            + "         \"verificationCardSetId\":\"9a0\",\n" + "         \"alias\":\"Default VCS Alias\",\n"
            + "         \"numberOfVotingCardsToGenerate\":10,\n" + "         \"electionEvent\":{  \n"
            + "            \"id\":\"8324e5f17dc9475d891af3051a22e759\"\n" + "         },\n"
            + "         \"ballotBox\":{  \n" + "            \"id\":\"4b88e5ec5fc14e14934c2ef491fe2de7\"\n"
            + "         },\n" + "\t \"status\": \"SIGNED\"\n" + "      }]}";

        when(votingCardSetRepositoryMock.list(any())).thenReturn(votingCardSet);
        when(pathResolver.resolve(any())).thenReturn(Paths.get("src/test/resources/votingcardsetservice/"));

        assertThat(sut.sign(electionEventId, votingCardSetId, SigningTestData.PRIVATE_KEY_PEM), is(false));
    }

    private void verifyVoteVerificationJSONs(final PublicKey publicKey, final Path outputPath) throws IOException {

        Path signedVerificationCardSetData = Paths.get(outputPath.toString(), "voteVerification/9a0/",
            ConfigConstants.CONFIG_FILE_NAME_SIGNED_VERIFICATIONSET_DATA);
        Path signedVerificationContextData = Paths.get(outputPath.toString(), "voteVerification/9a0/",
            ConfigConstants.CONFIG_FILE_NAME_SIGNED_VERIFICATION_CONTEXT_DATA);

        assertThat(exists(signedVerificationCardSetData), is(true));
        assertThat(exists(signedVerificationContextData), is(true));

        ConfigObjectMapper mapper = new ConfigObjectMapper();

        SignedObject signedVerificationCardSetDataObj =
            mapper.fromJSONFileToJava(signedVerificationCardSetData.toFile(), SignedObject.class);
        String signatureVerCardSetData = signedVerificationCardSetDataObj.getSignature();

        SignedObject signedVerificationContextDataObj =
            mapper.fromJSONFileToJava(signedVerificationContextData.toFile(), SignedObject.class);
        String signatureVerCardContextData = signedVerificationContextDataObj.getSignature();

        JSONVerifier verifier = new JSONVerifier();

        verifier.verify(publicKey, signatureVerCardSetData, VerificationCardSetData.class);
        verifier.verify(publicKey, signatureVerCardContextData, VoteVerificationContextData.class);

        Files.deleteIfExists(signedVerificationCardSetData);
        Files.deleteIfExists(signedVerificationContextData);
    }

    private void verifyExtendedAuth(final CSVVerifier csvVerifier, final PublicKey publicKey, final Path outputPath)
            throws IOException, GeneralCryptoLibException {

        Path extendedAuthPath = Paths.get(outputPath.toString(), "extendedAuthentication", "355/");

        List<Path> extendeAuthFiles = Files.walk(extendedAuthPath, 1)
            .filter(filePath -> filePath.getFileName().toString().endsWith(ConfigConstants.CSV))
            .collect(Collectors.toList());

        for (Path csvFilePath : extendeAuthFiles) {
            if (csvFilePath.getFileName().toString().endsWith(ConfigConstants.CSV)) {
                String csvAndSignature = concatenateCSVAndSignature(csvFilePath);
                Path tempFile = Files.createTempFile("tmp", ".tmp");
                Files.write(tempFile, csvAndSignature.getBytes(StandardCharsets.UTF_8));
                assertThat(csvVerifier.verify(publicKey, tempFile), is(true));
                Files.deleteIfExists(Paths.get(csvFilePath.toString() + ConfigConstants.SIGN));
            }
        }
    }

    private void verifyVoteVerificationCSVs(final CSVVerifier csvVerifier, final PublicKey publicKey,
            final Path outputPath) throws IOException, GeneralCryptoLibException {
        Path voteVerificationPath = Paths.get(outputPath.toString(), "voteVerification/9a0/");

        List<Path> voteVerificationFiles = Files.walk(voteVerificationPath, 1)
            .filter(filePath -> filePath.getFileName().toString().endsWith(ConfigConstants.CSV))
            .collect(Collectors.toList());

        for (Path csvFilePath : voteVerificationFiles) {
            if (csvFilePath.getFileName().toString().endsWith(ConfigConstants.CSV)) {
                String csvAndSignature = concatenateCSVAndSignature(csvFilePath);
                Path tempFile = Files.createTempFile("tmp", ".tmp");
                Files.write(tempFile, csvAndSignature.getBytes(StandardCharsets.UTF_8));
                assertThat(csvVerifier.verify(publicKey, tempFile), is(true));
                Files.deleteIfExists(Paths.get(csvFilePath.toString() + ConfigConstants.SIGN));
            }
        }
    }

    private void verifyVoterMaterialCSVs(final CSVVerifier csvVerifier, final PublicKey publicKey,
            final Path outputPath) throws IOException, GeneralCryptoLibException {
        Path voterMaterialPath = Paths.get(outputPath.toString(), "voterMaterial/355/");

        List<Path> voterMaterialFiles = Files.walk(voterMaterialPath, 1)
            .filter(filePath -> filePath.getFileName().toString().endsWith(ConfigConstants.CSV))
            .collect(Collectors.toList());

        for (Path csvFilePath : voterMaterialFiles) {
            if (csvFilePath.getFileName().toString().endsWith(ConfigConstants.CSV)) {
                String csvAndSignature = concatenateCSVAndSignature(csvFilePath);
                Path tempFile = Files.createTempFile("tmp", ".tmp");
                Files.write(tempFile, csvAndSignature.getBytes(StandardCharsets.UTF_8));
                assertThat(csvVerifier.verify(publicKey, tempFile), is(true));
                Files.deleteIfExists(Paths.get(csvFilePath.toString() + ConfigConstants.SIGN));
            }
        }
    }

    private String concatenateCSVAndSignature(final Path filePath) throws IOException {
        Path signedFile = Paths.get(filePath.toString() + ConfigConstants.SIGN);
        String csvText = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8);
        String signatureB64 = new String(Files.readAllBytes(signedFile), StandardCharsets.UTF_8);
        return csvText + "\n" + signatureB64;
    }

    @Test(expected = IllegalArgumentException.class)
    public void generateElectionEventIdNull()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        sut.generate(VOTING_CARD_SET_ID, null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void generateElectionEventIdEmpty()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        sut.generate(VOTING_CARD_SET_ID, "");
    }

    @Test(expected = IllegalArgumentException.class)
    public void generateVotingCardSetIdNull()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        sut.generate(null, ELECTION_EVENT_ID);
    }

    @Test(expected = IllegalArgumentException.class)
    public void generateVotingCardSetIdEmpty()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        sut.generate("", ELECTION_EVENT_ID);
    }

    @Test
    public void generateEncryptParamsGenerationFails()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        DataGeneratorResponse resultMock = new DataGeneratorResponse();
        resultMock.setSuccessful(false);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID)).thenReturn(resultMock);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateReadBallotBoxId4VotingCardSetIdEmpty()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {
        DataGeneratorResponse resultMock = new DataGeneratorResponse();
        resultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID)).thenReturn(resultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        setStatusForVotingCardSetFromRepository("VCS_DOWNLOADED", votingCardSetRepositoryMock);
        
        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateReadBallotId4BallotBoxIdEmpty()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse resultMock = new DataGeneratorResponse();
        resultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID)).thenReturn(resultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn("");
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        setStatusForVotingCardSetFromRepository("VCS_DOWNLOADED", votingCardSetRepositoryMock);

        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateGenerateBallotDataFails()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse encryptParamsResultMock = new DataGeneratorResponse();
        encryptParamsResultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID))
            .thenReturn(encryptParamsResultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(ballotBoxRepository.getBallotId(BALLOT_BOX_ID)).thenReturn(BALLOT_ID);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(false);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateGenerateBallotBoxDataDoneFails()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse encryptParamsResultMock = new DataGeneratorResponse();
        encryptParamsResultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID))
            .thenReturn(encryptParamsResultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(ballotBoxRepository.getBallotId(BALLOT_BOX_ID)).thenReturn(BALLOT_ID);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(ballotBoxRepository.find(BALLOT_BOX_ID)).thenReturn(getBallotBoxWithStatus(Status.LOCKED));
        DataGeneratorResponse ballotBoxDataResultMock = new DataGeneratorResponse();
        ballotBoxDataResultMock.setSuccessful(false);
        when(ballotBoxDataGeneratorServiceMock.generate(BALLOT_BOX_ID, ELECTION_EVENT_ID))
            .thenReturn(ballotBoxDataResultMock);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateFailsGenerateBallotBoxDataDone()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse encryptParamsResultMock = new DataGeneratorResponse();
        encryptParamsResultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID))
            .thenReturn(encryptParamsResultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(ballotBoxRepository.getBallotId(BALLOT_BOX_ID)).thenReturn(BALLOT_ID);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(ballotBoxRepository.find(BALLOT_BOX_ID)).thenReturn(getBallotBoxWithStatus(Status.LOCKED));
        DataGeneratorResponse ballotBoxDataResultMock = new DataGeneratorResponse();
        ballotBoxDataResultMock.setSuccessful(true);
        when(ballotBoxDataGeneratorServiceMock.generate(BALLOT_BOX_ID, ELECTION_EVENT_ID))
            .thenReturn(ballotBoxDataResultMock);
        when(configurationEntityStatusServiceMock.update(Mockito.anyString(), Mockito.anyString(),
            Mockito.any(BallotBoxRepository.class))).thenReturn("");
        DataGeneratorResponse votingCardSetDataResultMock = new DataGeneratorResponse();
        votingCardSetDataResultMock.setSuccessful(false);
        when(votingCardSetDataGeneratorServiceMock.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID))
            .thenReturn(votingCardSetDataResultMock);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        setStatusForVotingCardSetFromRepository("VCS_DOWNLOADED", votingCardSetRepositoryMock);
        
        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateFailsGenerateBallotBoxDataNotDone()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse encryptParamsResultMock = new DataGeneratorResponse();
        encryptParamsResultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID))
            .thenReturn(encryptParamsResultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(ballotBoxRepository.getBallotId(BALLOT_BOX_ID)).thenReturn(BALLOT_ID);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(ballotBoxRepository.find(BALLOT_BOX_ID)).thenReturn(getBallotBoxWithStatus(Status.READY));
        DataGeneratorResponse votingCardSetDataResultMock = new DataGeneratorResponse();
        votingCardSetDataResultMock.setSuccessful(false);
        when(votingCardSetDataGeneratorServiceMock.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID))
            .thenReturn(votingCardSetDataResultMock);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        assertFalse(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void generateGenerateBallotBoxDataNotDone()
            throws IOException, ResourceNotFoundException, InvalidStatusTransitionException {

        DataGeneratorResponse encryptParamsResultMock = new DataGeneratorResponse();
        encryptParamsResultMock.setSuccessful(true);
        when(encryptionParametersDataGeneratorServiceMock.generate(ELECTION_EVENT_ID))
            .thenReturn(encryptParamsResultMock);
        when(votingCardSetRepositoryMock.getBallotBoxId(VOTING_CARD_SET_ID)).thenReturn(BALLOT_BOX_ID);
        when(ballotBoxRepository.getBallotId(BALLOT_BOX_ID)).thenReturn(BALLOT_ID);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(ballotBoxRepository.find(BALLOT_BOX_ID)).thenReturn(getBallotBoxWithStatus(Status.READY));
        DataGeneratorResponse votingCardSetDataResultMock = new DataGeneratorResponse();
        votingCardSetDataResultMock.setSuccessful(true);
        when(votingCardSetDataGeneratorServiceMock.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID))
            .thenReturn(votingCardSetDataResultMock);
        when(configurationEntityStatusServiceMock.update(Mockito.anyString(), Mockito.anyString(),
            Mockito.any(VotingCardSetRepository.class))).thenReturn("");
        when(votingCardSetRepositoryMock.list(any())).thenReturn(listVotingCardSets("VCS_DOWNLOADED"));

        assertTrue(sut.generate(VOTING_CARD_SET_ID, ELECTION_EVENT_ID).isSuccessful());
    }

    @Test
    public void download()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, GeneralCryptoLibException, URISyntaxException, PayloadStorageException {
        setStatusForVotingCardSetFromRepository(Status.COMPUTED.name(), votingCardSetRepositoryMock);

        Map<String, Object> ballotBoxQueryMap = new HashMap<>();
        ballotBoxQueryMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, ELECTION_EVENT_ID);
        ballotBoxQueryMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, BALLOT_BOX_ID);
        Map<String, Object> ballotQueryMap = new HashMap<>();
        ballotQueryMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, ELECTION_EVENT_ID);
        ballotQueryMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, BALLOT_ID);
        String encryptedBck = "3";
        String computedBck = "5";
        VerificationCardIdToNodesContributions verificationCardIdToNodesContributionsMock =
                new VerificationCardIdToNodesContributions();
        verificationCardIdToNodesContributionsMock.setVerificationCardId(VERIFICATION_CARD_ID);
        verificationCardIdToNodesContributionsMock.addOptionRepresentationContribution("computePrePartialResultMock");
        verificationCardIdToNodesContributionsMock.setEncryptedBallotCastingKey(encryptedBck);
        verificationCardIdToNodesContributionsMock.setBallotCastingKeyContributions(singletonList(computedBck));
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));
        Path folder = basePath.resolve(ELECTION_EVENT_ID).resolve(ONLINE_PATH)
                .resolve(VOTE_VERIFICATION_FOLDER).resolve(VERIFICATION_CARD_SET_ID);

        when(ballotBoxRepository.find(ballotBoxQueryMap)).thenReturn(getBallotBoxWithStatus(Status.READY));
        when(ballotRepository.find(ballotQueryMap)).thenReturn(BALLOT_JSON);
        when(electionEventRepository.find(ELECTION_EVENT_ID)).thenReturn(ELECTION_EVENT_JSON);
        when(pathResolver.resolve(any())).thenReturn(basePath);
        when(ballotDataGeneratorServiceMock.generate(BALLOT_ID, ELECTION_EVENT_ID)).thenReturn(ballotDataResultMock);
        when(objectMapperMock.writeValueAsString(anyObject())).thenReturn(COMPUTED_VALUES);
        when(configurationEntityStatusServiceMock.update(anyString(), anyString(), anyObject())).thenReturn("");
        when(choiceCodeGenerationRequestPayloadRepository.getCount(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(3);
        when(votingCardSetChoiceCodesServiceMock.download(anyString(), anyString(), anyInt()))
                .thenReturn(new ByteArrayInputStream(new byte[]{1,2,3}),
                    new ByteArrayInputStream(new byte[]{4,5,6}), 
                    new ByteArrayInputStream(new byte[]{7,8,9}));
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.download(VOTING_CARD_SET_ID, ELECTION_EVENT_ID);

        assertTrue(exists(folder.resolve(ConfigConstants.CONFIG_FILE_NAME_NODE_CONTRIBUTIONS + ".0" + Constants.JSON)));
        assertTrue(exists(folder.resolve(ConfigConstants.CONFIG_FILE_NAME_NODE_CONTRIBUTIONS + ".1" + Constants.JSON)));
        assertTrue(exists(folder.resolve(ConfigConstants.CONFIG_FILE_NAME_NODE_CONTRIBUTIONS + ".2" + Constants.JSON)));
    }

    @Test(expected = InvalidStatusTransitionException.class)
    public void download_invalid_status()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, GeneralCryptoLibException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        setStatusForVotingCardSetFromRepository("SIGNED", votingCardSetRepositoryMock);
        sut.download(VOTING_CARD_SET_ID, ELECTION_EVENT_ID);
    }

    private void copyTestFiles() throws IOException, URISyntaxException {
        sourceTestFilesPath = getPathOfFileInResources(Paths.get("VotingCardSetServiceTest"));
        tempDirectory = Files.createTempDirectory("VotingCardSetServiceTest");
        FileUtils.copyDirectory(sourceTestFilesPath.toFile(), tempDirectory.toFile());
    }

    @Test
    public void testBeanToJson() throws IOException, URISyntaxException {

        copyTestFiles();

        SignRequest signRequest = getSignRequest();
        Gson gson = new Gson();
        String jsonStr = gson.toJson(signRequest);
        System.out.println(jsonStr);

        SignRequest fromJson = gson.fromJson(jsonStr, SignRequest.class);

        assertEquals(signRequest.getElectionEventId(), fromJson.getElectionEventId());
        assertEquals(signRequest.getCertificatesChain().size(), fromJson.getCertificatesChain().size());

    }

    private SignRequest getSignRequest() throws IOException {
        SignRequest signRequest = new SignRequest();
        signRequest.setVotingCardSetId(VOTING_CARD_SET_ID);
        signRequest.setElectionEventId(ELECTION_EVENT_ID);
        signRequest.setPrivateKeyPEM(SigningTestData.PRIVATE_KEY_PEM);
        signRequest.setCertificatesChain(getCertificatesChain());
        return signRequest;
    }

    private List<String> getCertificatesChain() throws IOException {

        String certificatesPathStr = tempDirectory.toAbsolutePath().toString();

        Path tenantCertPath = Paths.get(certificatesPathStr, adapterSignTenantCertName);

        Path rootCertPath = Paths.get(certificatesPathStr, adapterSignRootCertName);

        List<String> certificates = new ArrayList<>();

        byte[] tenantCertContent = Files.readAllBytes(tenantCertPath);
        byte[] rootCertContent = Files.readAllBytes(rootCertPath);

        certificates.add(new String(tenantCertContent));
        certificates.add(new String(rootCertContent));

        LOGGER.info("Used Tenant certificate: " + adapterSignTenantCertName);
        LOGGER.info("Used Root certificate: " + adapterSignRootCertName);

        return certificates;
    }



}
