/*
 * 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 com.fasterxml.jackson.core.JsonProcessingException;
import static java.nio.file.Files.write;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
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.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 com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalEncrypter;
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.PayloadSignatureException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadVerificationException;
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.verify.PayloadVerifier;
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 mockit.Deencapsulation;

/**
 * JUnit for the class {@link VotingCardSetService}.
 */
@RunWith(MockitoJUnitRunner.class)
public class VotingCardSetComputationServiceTest 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 COMPUTED_VALUES = "{\"100003\":{\"value\":153099029,\"p\":163705,\"q\":81852594971}";

    private static final String PRECOMPUTED_VALUES_PATH = "computeTest";

    @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 PayloadVerifier payloadVerifier;

    @Mock
    private PlatformRootCAService platformRootCAService;

    @InjectMocks
    private final VotingCardSetComputationService sut = new VotingCardSetComputationService();
    
    @Mock IdleStatusService idleStatusService;

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

    @Before
    public void beforeEachTest() throws ResourceNotFoundException, PayloadVerificationException {
        Deencapsulation.setField(sut, "pathResolver", pathResolver);
        Deencapsulation.setField(sut, "secureLogger", Mockito.mock(SecureLoggingWriter.class));

        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(votingCardSetRepositoryMock.getVerificationCardSetId(VOTING_CARD_SET_ID))
            .thenReturn(VERIFICATION_CARD_SET_ID);

        when(payloadVerifier.isValid(any(), any())).thenReturn(true);
    }

    @Test
    public void compute()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, GeneralCryptoLibException,
            URISyntaxException, PayloadStorageException, PayloadSignatureException, JsonProcessingException,
            PayloadVerificationException {
        setStatusForVotingCardSetFromRepository(Status.PRECOMPUTED.name(), votingCardSetRepositoryMock);

        setStatusForVotingCardSetFromRepository(Status.PRECOMPUTED.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);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));
        ChoiceCodeGenerationReqPayload payload = 
                new ChoiceCodeGenerationReqPayload("tenantId", ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID, 0, emptyList());

        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(ballotCastingKeyGenerator.generateBallotCastingKey()).thenReturn("123");
        when(choiceCodeGenerationRequestPayloadRepository.getCount(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(1);
        when(choiceCodeGenerationRequestPayloadRepository.retrieve(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID, 0)).thenReturn(payload);

        Path representationsFile = basePath.resolve(ELECTION_EVENT_ID).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION).resolve(VERIFICATION_CARD_SET_ID)
            .resolve(ConfigConstants.CONFIG_FILE_NAME_ELECTION_OPTION_REPRESENTATIONS);
        write(representationsFile, singletonList("2"));
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.compute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID);

        verify(votingCardSetChoiceCodesServiceMock, times(1)).sendToCompute(payload);
        verify(configurationEntityStatusServiceMock, times(1)).update(anyString(), anyString(), anyObject());
    }

    @Test(expected = IllegalArgumentException.class)
    public void compute_invalid_params() throws Exception {
        setStatusForVotingCardSetFromRepository(Status.SIGNED.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);
        DataGeneratorResponse ballotDataResultMock = new DataGeneratorResponse();
        ballotDataResultMock.setSuccessful(true);
        Path basePath = getPathOfFileInResources(Paths.get(PRECOMPUTED_VALUES_PATH));

        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(idleStatusService.isIdReady(anyString())).thenReturn(true);

        sut.compute("", ELECTION_EVENT_ID);
    }

    @Test(expected = InvalidStatusTransitionException.class)
    public void compute_invalid_status()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, PayloadStorageException,
            PayloadSignatureException, JsonProcessingException, PayloadVerificationException {
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        setStatusForVotingCardSetFromRepository("SIGNED", votingCardSetRepositoryMock);
        sut.compute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID);
    }

    @Test
    public void compute_invalid_signature()
            throws ResourceNotFoundException, InvalidStatusTransitionException, IOException, PayloadStorageException,
            PayloadSignatureException, JsonProcessingException, PayloadVerificationException {
        setStatusForVotingCardSetFromRepository(Status.PRECOMPUTED.name(), votingCardSetRepositoryMock);
        when(payloadVerifier.isValid(any(), any())).thenReturn(false);
        when(idleStatusService.isIdReady(anyString())).thenReturn(true);
        sut.compute(VOTING_CARD_SET_ID, ELECTION_EVENT_ID);

        verify(votingCardSetChoiceCodesServiceMock, times(0)).sendToCompute(any());
    }
}
