/*
 * 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.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
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.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalEncrypter;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalKeyPairGenerator;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
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.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.choices.codes.BallotCastingKeyGenerator;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignature;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignatureException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationReqPayload;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.sign.CryptoTestData;
import com.scytl.products.ov.commons.sign.PayloadSigner;
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.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.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.ChoiceCodeGenerationRequestPayloadFileSystemRepository;
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 mockit.Deencapsulation;

/**
 * JUnit for the class {@link VotingCardSetService}.
 */
@RunWith(MockitoJUnitRunner.class)
public class VotingCardSetPrecomputationServiceTest 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 String VOTING_CARD_SET_ID = "355";

    private static final String BALLOT_BOX_ID = "4b88e5ec5fc14e14934c2ef491fe2de7";

    private static final String BALLOT_ID = "dd5bd34dcf6e4de4b771a92fa38abc11";

    private static final String VERIFICATION_CARD_SET_ID = "9a0";

    private static final String PRECOMPUTED_VALUES_PATH = "computeTest";

    private static final String ADMIN_BOARD_ID = "dc742be1d49b42ee83cfe1652a8170ac";

    private static final int SIGNING_KEY_SIZE = 1024;

    private static String signingKeyPEM = "";

    @Mock
    private EncryptionParametersDataGeneratorService encryptionParametersDataGeneratorServiceMock;

    @Mock
    private PathResolver pathResolver;

    @Mock
    private ObjectMapper objectMapperMock;

    @Mock
    private VotingCardSetRepository votingCardSetRepositoryMock;

    @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 ChoiceCodeGenerationRequestPayloadRepository choiceCodeGenerationRequestPayloadRepository;

    @Mock
    private SecureLoggingWriter secureLogger;

    @Mock
    private AdminBoardService adminBoardService;

    @Mock
    private PayloadSigner payloadSigner;
    
    @Mock
    private PrimitivesService primitivesService;

    @Mock IdleStatusService idleStatusService;

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

    @BeforeClass
    public static void setUpCrypto()
            throws NoSuchAlgorithmException, NoSuchProviderException, GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());

        // Generate the signing key pair.
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
        generator.initialize(SIGNING_KEY_SIZE);
        KeyPair signingKeyPair = generator.generateKeyPair();
        // Store the private key PEM.
        signingKeyPEM = PemUtils.privateKeyToPem(signingKeyPair.getPrivate());
    }

    @Before
    public void beforeEachTest() throws CertificateManagementException, PayloadSignatureException {
        Deencapsulation.setField(sut, "pathResolver", pathResolver);
        Deencapsulation.setField(sut, "secureLogger", secureLogger);
        Deencapsulation.setField(sut, "chunkSize", 3);

        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"));

        when(adminBoardService.getCertificateChain(anyString())).thenReturn(new X509Certificate[] {});

        PayloadSignature mockPayloadSignature = Mockito.mock(PayloadSignature.class);
        when(payloadSigner.sign(any(), any(), any())).thenReturn(mockPayloadSignature);
    }

    private void setUpService(int numberOfVotingCards)
            throws URISyntaxException, GeneralCryptoLibException, ResourceNotFoundException, IOException {
        setStatusForVotingCardSetFromRepository(Status.LOCKED.name(), votingCardSetRepositoryMock);

        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));

        // Produce a stream of verification cards.
        when(verificationCardDataGeneratorServiceMock.precompute(anyString(), anyString(), anyInt()))
            .then(new Answer<BufferedReader>() {
                @Override
                public BufferedReader answer(InvocationOnMock invocation) {
                    int count = invocation.getArgumentAt(2, Integer.class);
                    return getVerificationCardIds(count);
                }
            });

        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);
        ZpSubgroup group = new ZpSubgroup(BigInteger.valueOf(2), P, Q);
        ZpGroupElement element = new ZpGroupElement(BigInteger.valueOf(4), group);
        ElGamalPublicKey key = new ElGamalPublicKey(singletonList(element), group);
        Exponent exponent = new Exponent(Q, BigInteger.valueOf(2));
        ElGamalComputationsValues values =
            new ElGamalComputationsValues(asList(element.exponentiate(exponent), element.exponentiate(exponent)));
        ElGamalEncrypterValues ciphertext = new ElGamalEncrypterValues(exponent, values);

        when(pathResolver.resolve(any())).thenReturn(basePath);
        doNothing().when(votingCardSetRepositoryMock).updateRelatedVerificationCardSet(anyString(), anyString());
        when(configurationEntityStatusServiceMock.update(anyString(), anyString(), anyObject())).thenReturn("");
        when(ballotBoxRepository.find(ballotBoxQueryMap)).thenReturn(getBallotBoxWithStatus(Status.READY));
        when(ballotRepository.find(ballotQueryMap)).thenReturn(BALLOT_JSON);
        when(votingCardSetRepositoryMock.getVerificationCardSetId(VOTING_CARD_SET_ID))
            .thenReturn(VERIFICATION_CARD_SET_ID);
        when(electionEventRepository.find(ELECTION_EVENT_ID)).thenReturn(ELECTION_EVENT_JSON);
        when(electionEventService.getPrimeEncryptionPublicKey(ELECTION_EVENT_ID)).thenReturn(key);
        when(elGamalService.createEncrypter(key)).thenReturn(encrypter);
        when(encrypter.encryptGroupElements(any())).thenReturn(ciphertext);
        when(codec.encodeList(any())).thenReturn("encodedEncryptedRepresentations");
        when(ballotCastingKeyGenerator.generateBallotCastingKey()).thenReturn("123");

        setUpVCSJson("LOCKED", votingCardSetRepositoryMock);
    }

    @Test
    public void precompute()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, URISyntaxException,
            GeneralCryptoLibException, PayloadStorageException, PayloadSignatureException {
        setUpService(10);
        
        ElGamalKeyPair elGamalKeyPair = generateElGamalKeyPairForTest();
        CryptoAPIElGamalKeyPairGenerator keyGeneratorMock = Mockito.mock(CryptoAPIElGamalKeyPairGenerator.class);
        when(keyGeneratorMock.generateKeys(any(), Matchers.eq(1))).thenReturn(elGamalKeyPair);
        when(elGamalService.getElGamalKeyPairGenerator()).thenReturn(keyGeneratorMock);
        
        when(primitivesService.getHash(Matchers.any(byte[].class))).thenReturn(new BigInteger("121").toByteArray());
        
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.precompute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID, signingKeyPEM, ADMIN_BOARD_ID);

        Path path = ChoiceCodeGenerationRequestPayloadFileSystemRepository.getStoragePath(pathResolver,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID, 0);

        ChoiceCodeGenerationReqPayload payload =
            objectMapper.readValue(path.toFile(), ChoiceCodeGenerationReqPayload.class);
        
        assertEquals(ELECTION_EVENT_ID, payload.getElectionEventId());
        assertEquals(VERIFICATION_CARD_SET_ID, payload.getVerificationCardSetId());
        verify(choiceCodeGenerationRequestPayloadRepository, times(4)).store(any());
    }

    private ElGamalKeyPair generateElGamalKeyPairForTest() throws GeneralCryptoLibException {
        ElGamalService elGamalService = new ElGamalService();
        ElGamalEncryptionParameters encParams =
                new ElGamalEncryptionParameters(CryptoTestData.P, CryptoTestData.Q, CryptoTestData.G);
        return elGamalService.getElGamalKeyPairGenerator().generateKeys(encParams, 1);
    }

    @Test
    @Ignore // Performance test -- must not be part of the general test suite
    public void precompute_many_vcs()
            throws URISyntaxException, GeneralCryptoLibException, ResourceNotFoundException,
            InvalidStatusTransitionException, IOException, PayloadStorageException, PayloadSignatureException {
        int numberOfVotingCards = 50_000;
        // Approximate size per CCG input is 105 overhead plus 121 per voting
        // card.
        int minimumFileSize = 105 + (numberOfVotingCards * 121);
        setUpService(numberOfVotingCards);

        sut.precompute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID, signingKeyPEM, ADMIN_BOARD_ID);

        Path path = ChoiceCodeGenerationRequestPayloadFileSystemRepository.getStoragePath(pathResolver,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID, 0);

        assertTrue("The file is smaller than expected", minimumFileSize <= path.toFile().length());
    }

    @Test(expected = IllegalArgumentException.class)
    public void precompute_invalid_params()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, URISyntaxException,
            GeneralCryptoLibException, PayloadStorageException, PayloadSignatureException {
        setStatusForVotingCardSetFromRepository(Status.LOCKED.name(), votingCardSetRepositoryMock);

        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));
        final int numVerificationCards = 10;

        when(pathResolver.resolve(any())).thenReturn(basePath);
        when(verificationCardDataGeneratorServiceMock.precompute(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID,
            numVerificationCards)).thenReturn(getVerificationCardIds(numVerificationCards));
        doNothing().when(votingCardSetRepositoryMock).updateRelatedVerificationCardSet(anyString(), anyString());
        when(configurationEntityStatusServiceMock.update(anyString(), anyString(), anyObject())).thenReturn("");
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.precompute("", "", "", "");
    }

    @Test(expected = InvalidStatusTransitionException.class)
    public void precompute_invalid_status()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, URISyntaxException,
            GeneralCryptoLibException, PayloadStorageException, PayloadSignatureException {
        setStatusForVotingCardSetFromRepository(Status.SIGNED.name(), votingCardSetRepositoryMock);

        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));
        when(pathResolver.resolve(any())).thenReturn(basePath);

        int numVotingCards = 10;
        BufferedReader verificationCardIds = getVerificationCardIds(numVotingCards);
        when(verificationCardDataGeneratorServiceMock.precompute(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID,
            numVotingCards)).thenReturn(verificationCardIds);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.precompute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID, signingKeyPEM, ADMIN_BOARD_ID);
    }

    @Test(expected = PayloadSignatureException.class)
    public void precompute_invalid_signing_parameters()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, URISyntaxException,
            GeneralCryptoLibException, PayloadStorageException, PayloadSignatureException {
        setUpService(1);
        
        ElGamalKeyPair elGamalKeyPair = generateElGamalKeyPairForTest();
        CryptoAPIElGamalKeyPairGenerator keyGeneratorMock = Mockito.mock(CryptoAPIElGamalKeyPairGenerator.class);
        when(keyGeneratorMock.generateKeys(any(), Matchers.eq(1))).thenReturn(elGamalKeyPair);
        when(elGamalService.getElGamalKeyPairGenerator()).thenReturn(keyGeneratorMock);
        
        when(primitivesService.getHash(Matchers.any(byte[].class))).thenReturn(new BigInteger("121").toByteArray());
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.precompute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID, "", "");
    }

    /**
     * Generates a stream of the requested number of verification card IDs.
     * 
     * @param count
     *            the number of elements to generate
     * @return a stream with the requested number of elements
     */
    private static BufferedReader getVerificationCardIds(final int count) {
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream("1\n2\n3\n".getBytes())));
    }
}
