/*
 * 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.vv.domain.model.rule;

import static org.hamcrest.core.Is.is;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import javax.json.Json;
import javax.json.JsonObjectBuilder;

import org.junit.Assert;
import org.junit.Before;
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.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.proofs.ProofsServiceAPI;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.proofs.cryptoapi.ProofVerifierAPI;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.domain.model.vote.Vote;
import com.scytl.products.ov.commons.beans.exceptions.EntryPersistenceException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.vv.domain.model.content.ElectoralAuthorityEntity;
import com.scytl.products.ov.vv.domain.model.content.ElectoralAuthorityRepository;
import com.scytl.products.ov.vv.domain.model.content.VerificationContentEntity;
import com.scytl.products.ov.vv.domain.model.content.VerificationContentRepository;
import com.scytl.products.ov.vv.domain.model.verification.Verification;
import com.scytl.products.ov.vv.domain.model.verification.VerificationRepository;
import com.scytl.products.ov.vv.domain.model.verificationset.VerificationSetEntity;
import com.scytl.products.ov.vv.domain.model.verificationset.VerificationSetRepository;
import com.scytl.products.ov.vv.infrastructure.log.SecureLoggerHelper;
import com.scytl.products.ov.vv.infrastructure.persistence.ElectoralAuthorityRepositoryDecorator;
import com.scytl.products.ov.vv.infrastructure.persistence.VerificationContentRepositoryDecorator;
import com.scytl.products.ov.vv.infrastructure.persistence.VerificationRepositoryDecorator;
import com.scytl.products.ov.vv.infrastructure.persistence.VerificationSetRepositoryDecorator;

@RunWith(MockitoJUnitRunner.class)
public class PlainTextEqualityProofRuleTest {

    private final String VERIFICATION_CHOICE_CODES_PUBLIC_KEY =
        "eyJwdWJsaWNLZXkiOnsienBTdWJncm91cCI6eyJwIjoiQVBPZU5GbmI3SFNoYlNDUnEyVmt6cFVzY3RBdmE2ODBGYkIzU2hlSFFjWWIiLCJxIjoiZWM4YUxPMzJPbEMya0VqVnNySm5TcFk1YUJlMTE1b0syRHVsQzhPZzR3MD0iLCJnIjoiQXc9PSJ9LCJlbGVtZW50cyI6WyJBT3h0TXZqVjFpb2U5ZDRndVBoTXdibjdPUkVXQ3JhNVRoMlZvQ1plVm9ZcyIsIkFLUnVZT2N0bFE1VHFjLzdaNlZZaGdXZFdCM0FCVWI0cjJvVnpLQmJwcHN5IiwiTC8vcGFnM09PSEpyZE1VUTc3RjhUSm1lTnVpell0M3VNcGw0L0RBRkwwND0iLCJBTHl6YU44SFAwU3JDRksraTdhN1dvTVVtRnFFRE5MQ1ZKYmJzQkZrKytReSIsIkFMU3ZjZlRpWFhmVDFpbC9iUnNJMHJadlU2RUd5TWlpZGpQWlNnVE5ZYUxpIiwiQU1VNmxNbmlqS243SnhqVnAraXIvZFFUbk1LQjBVVllhYU9xRElVOE1KUVIiLCJBTnlQYlIvNnNzYmRTaXNYOWVveE42RHNLdTRiNk43Q0dCQmFIalhOamFybCIsIkFwQXRoc2ZpdXRZMXNGWHNEd3k3LzllU2trbU1WNmJLaThJRE9KL2xsT1k9IiwiWHp1dEU0SWJxdVFnY1pORm9POUtpLzhubmNaQjI5aFVGUjk4M29mT0JXYz0iLCJVWlNHbTNDdGVYSjJWNkFzdEt6bW12c25ESy9qRE1LTSt3QVE1dGFISWVJPSIsIkJ6TFRxdFNwL1MzUHFkc20vOGJkRTlTOUF1eGlTdkNLdFFERGhuMTJndUE9IiwiQUpmQzVRZnBhelVCdCtXNFF5VUJIV3Y2Vlg5S2cvaXY3Qm81N1RnRTVmRlgiLCJWci8wbysyNVhianNTTjgwUTgxd2dLYnI0ZmVuVWgyU21uUit6Mk9kQmZjPSIsIkFLYTJkeXlxODI4b2dVTTdxL0hlWm5VLzVxSFdITVVPZ0JRRlR2cFRORnJiIiwiQUxiZ2NXYnZVUzJDb216SnpwTEdjNzNqcE9GbFZDUlR4ZXVnLytITGpERFMiXX19";

    private final String ELECTORAL_AUTHORITY_PUBLIC_KEY_BASE64 =
        "eyJwdWJsaWNLZXkiOnsienBTdWJncm91cCI6eyJxIjoiZWM4YUxPMzJPbEMya0VqVnNySm5TcFk1YUJlMTE1b0syRHVsQzhPZzR3MD0iLCJnIjoiQXc9PSIsInAiOiJBUE9lTkZuYjdIU2hiU0NScTJWa3pwVXNjdEF2YTY4MEZiQjNTaGVIUWNZYiJ9LCJlbGVtZW50cyI6WyJBTDdzQlg0bmtVbnJ5Y3dBcXlRTHhFRkhmeWVlMDRYTnJQMWo1ZzZWYkhZVSJdfX0=";

    private final String ELECTORAL_AUTHORITY_ID = "ac20b274bf48423d818c811c1da0713e";

    private final String VERIFICATION_SET_SIGNATURE = "20";

    private final String SIGNED_VERIFICATION_PUBLIC_KEY = "16";

    private final String VERIFICATION_CARD_KEYSTORE = "17";

    private final String CORRECTNESS_IDS = "1";

    private final String ELECTION_EVENT_ID = "2";

    private final String ENCRYPTED_OPTIONS = "3";

    private final String ENCRYPTES_WRITE_INS = "4";

    private final String EXPONENTIATION_PROOF = "5";

    private final String SCHNORR_PROOF = "7";

    private final String TENANT_ID = "8";

    private final String VERIFICATION_CARD_ID = "9";

    private final String VERIFICATION_CARD_PK_SIGNATURE = "10";

    private final String VERIFICATION_CARD_PUBLIC_KEY = "11";

    private final String VERIFICATION_CARD_SET_ID = "12";

    private final String VOTING_CARD_ID = "13";

    private final String AUTHENTICATION_TOKEN = "14";

    private final String CIPHER_TEXT_EXPONENTIATIONS = "3;4";

    private final String ENCRYPTED_PARTIAL_CHOICE_CODES = "2;6";

    @Mock
    private VerificationSetRepository verificationSetRepository;

    @InjectMocks
    private VerificationSetRepository verificationSetRepositoryDecorator = new VerificationSetRepositoryDecorator() {

        @Override
        public VerificationSetEntity update(VerificationSetEntity entity) throws EntryPersistenceException {
            return null;
        }

        @Override
        public VerificationSetEntity find(Integer id) {
            return null;
        }
    };

    @Mock
    private VerificationRepository verificationRepository;

    @InjectMocks
    private VerificationRepository verificationRepositoryDecorator = new VerificationRepositoryDecorator() {

        @Override
        public Verification update(Verification entity) throws EntryPersistenceException {
            return null;
        }

        @Override
        public Verification find(Integer id) {
            return null;
        }
    };

    @Mock
    private VerificationContentRepository verificationContentRepository;

    @InjectMocks
    private VerificationContentRepository verificationContentRepositoryDecorator =
        new VerificationContentRepositoryDecorator() {

            @Override
            public VerificationContentEntity update(VerificationContentEntity entity) throws EntryPersistenceException {
                return null;
            }
        };

    @Mock
    private ElectoralAuthorityRepository electoralAuthorityRepository;

    @InjectMocks
    private ElectoralAuthorityRepository electoralAuthorityRepositoryDecorator =
        new ElectoralAuthorityRepositoryDecorator() {

            @Override
            public ElectoralAuthorityEntity update(ElectoralAuthorityEntity entity) throws EntryPersistenceException {
                return null;
            }

            @Override
            public ElectoralAuthorityEntity find(Integer id) {
                return null;
            }
        };

    @Mock
    private ProofsServiceAPI proofsService;

    @Mock
    private Logger LOG;

    @InjectMocks
    private PlainTextEqualityProofRule rule = new PlainTextEqualityProofRule();

    @Mock
    private TrackIdInstance trackId;

    @Mock
    private SecureLoggingWriter secureLoggerWriter;

    @Mock
    private SecureLoggerHelper secureLoggerHelper;

    @InjectMocks
    private PlainTextEqualityProofRuleDecorator cut = new PlainTextEqualityProofRuleDecorator();

    @Before
    public void init() {
        cut.plainTextEqualityProofRule = rule;
        cut.plainTextEqualityProofRule.electoralAuthorityRepository = electoralAuthorityRepositoryDecorator;
        cut.plainTextEqualityProofRule.verificationContentRepository = verificationContentRepositoryDecorator;
        cut.plainTextEqualityProofRule.verificationRepository = verificationRepositoryDecorator;
        cut.plainTextEqualityProofRule.verificationSetRepository = verificationSetRepositoryDecorator;
    }

    @Test
    public void testVerification_Successful() throws ResourceNotFoundException, GeneralCryptoLibException {
        Vote vote = prepareMockVote();
        Verification verification = prepareMockVerification();
        VerificationContentEntity verificationContentEntity = prepapreVerificationContentMock();
        VerificationSetEntity verificationSetEntity = prepareVerificationSetMock();
        ElectoralAuthorityEntity electoralAuthorityEntity = prepareElectoralAuthorityMock();

        ProofVerifierAPI proofVerifier = Mockito.mock(ProofVerifierAPI.class);

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenReturn(verification);
        Mockito.when(verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationContentEntity);
        Mockito.when(verificationSetRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationSetEntity);
        Mockito.when(electoralAuthorityRepository.findByTenantIdElectionEventIdElectoralAuthorityId(TENANT_ID,
            ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID)).thenReturn(electoralAuthorityEntity);
        Mockito.when(proofsService.createProofVerifierAPI(Matchers.any())).thenReturn(proofVerifier);
        Mockito.when(proofVerifier.verifyPlaintextEqualityProof(Matchers.anyList(), Matchers.any(), Matchers.anyList(),
            Matchers.any(), Matchers.any())).thenReturn(true);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.SUCCESS));
    }

    @Test
    public void testVerification_Failed() throws ResourceNotFoundException, GeneralCryptoLibException {
        Vote vote = prepareMockVote();
        Verification verification = prepareMockVerification();
        VerificationContentEntity verificationContentEntity = prepapreVerificationContentMock();
        VerificationSetEntity verificationSetEntity = prepareVerificationSetMock();
        ElectoralAuthorityEntity electoralAuthorityEntity = prepareElectoralAuthorityMock();

        ProofVerifierAPI proofVerifier = Mockito.mock(ProofVerifierAPI.class);

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenReturn(verification);
        Mockito.when(verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationContentEntity);
        Mockito.when(verificationSetRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationSetEntity);
        Mockito.when(electoralAuthorityRepository.findByTenantIdElectionEventIdElectoralAuthorityId(TENANT_ID,
            ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID)).thenReturn(electoralAuthorityEntity);
        Mockito.when(proofsService.createProofVerifierAPI(Matchers.any())).thenReturn(proofVerifier);
        Mockito.when(proofVerifier.verifyPlaintextEqualityProof(Matchers.anyList(), Matchers.any(), Matchers.anyList(),
            Matchers.any(), Matchers.any())).thenReturn(false);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.FAILED));
    }

    @Test
    public void testVerification_VerificationNotFound() throws GeneralCryptoLibException, ResourceNotFoundException {
        Vote vote = prepareMockVote();

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenThrow(ResourceNotFoundException.class);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.FAILED));
    }

    @Test
    public void testVerification_VerificationContentNotFound()
            throws ResourceNotFoundException, GeneralCryptoLibException {
        Vote vote = prepareMockVote();
        Verification verification = prepareMockVerification();

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenReturn(verification);
        Mockito.when(verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenThrow(ResourceNotFoundException.class);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.FAILED));
    }

    @Test
    public void testVerification_VerificationSetNotFound() throws ResourceNotFoundException, GeneralCryptoLibException {
        Vote vote = prepareMockVote();
        Verification verification = prepareMockVerification();
        VerificationContentEntity verificationContentEntity = prepapreVerificationContentMock();

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenReturn(verification);
        Mockito.when(verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationContentEntity);
        Mockito.when(verificationSetRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenThrow(ResourceNotFoundException.class);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.FAILED));
    }

    @Test
    public void testVerification_ElectoralAuthorityNotFound()
            throws ResourceNotFoundException, GeneralCryptoLibException {
        Vote vote = prepareMockVote();
        Verification verification = prepareMockVerification();
        VerificationContentEntity verificationContentEntity = prepapreVerificationContentMock();
        VerificationSetEntity verificationSetEntity = prepareVerificationSetMock();

        Mockito.when(verificationRepository.findByTenantIdElectionEventIdVerificationCardId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_ID)).thenReturn(verification);
        Mockito.when(verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationContentEntity);
        Mockito.when(verificationSetRepository.findByTenantIdElectionEventIdVerificationCardSetId(TENANT_ID,
            ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(verificationSetEntity);
        Mockito.when(electoralAuthorityRepository.findByTenantIdElectionEventIdElectoralAuthorityId(TENANT_ID,
            ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID)).thenThrow(ResourceNotFoundException.class);

        ValidationError result = cut.execute(vote);

        Assert.assertThat(result.getValidationErrorType(), is(ValidationErrorType.FAILED));
    }

    private ElectoralAuthorityEntity prepareElectoralAuthorityMock() {
        JsonObjectBuilder json = Json.createObjectBuilder();
        json.add(PlainTextEqualityProofRule.JSON_PUBLIC_KEY, ELECTORAL_AUTHORITY_PUBLIC_KEY_BASE64);

        ElectoralAuthorityEntity electoralAuth = new ElectoralAuthorityEntity();

        electoralAuth.setElectionEventId(ELECTION_EVENT_ID);
        electoralAuth.setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID);
        electoralAuth.setId(1);
        electoralAuth.setTenantId(TENANT_ID);
        electoralAuth.setJson(json.build().toString());

        return electoralAuth;
    }

    private VerificationSetEntity prepareVerificationSetMock() {
        JsonObjectBuilder json = Json.createObjectBuilder();
        json.add(PlainTextEqualityProofRule.JSON_CHOICE_CODES_PUBLIC_KEY, VERIFICATION_CHOICE_CODES_PUBLIC_KEY);

        VerificationSetEntity verSet = new VerificationSetEntity();
        verSet.setElectionEventId(ELECTION_EVENT_ID);
        verSet.setJSON(json.build().toString());
        verSet.setSignature(VERIFICATION_SET_SIGNATURE);
        verSet.setTenantId(TENANT_ID);
        verSet.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);

        return verSet;
    }

    private VerificationContentEntity prepapreVerificationContentMock() {
        JsonObjectBuilder jsonEncryptionParams = Json.createObjectBuilder();
        jsonEncryptionParams.add(PlainTextEqualityProofRule.JSON_PARAMETER_GENERATOR, "2");
        jsonEncryptionParams.add(PlainTextEqualityProofRule.JSON_PARAMETER_P, "7");
        jsonEncryptionParams.add(PlainTextEqualityProofRule.JSON_PARAMETER_Q, "3");

        JsonObjectBuilder json = Json.createObjectBuilder();
        json.add(PlainTextEqualityProofRule.JSON_PARAMETER_ELECTORAL_AUTHORITY_ID, ELECTORAL_AUTHORITY_ID);
        json.add(PlainTextEqualityProofRule.JSON_PARAMETER_ENCRYPTION_PARAMETERS, jsonEncryptionParams);

        VerificationContentEntity verificationContent = new VerificationContentEntity();
        verificationContent.setElectionEventId(ELECTION_EVENT_ID);
        verificationContent.setJson(json.build().toString());
        verificationContent.setTenantId(TENANT_ID);
        verificationContent.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);

        return verificationContent;
    }

    private Verification prepareMockVerification() {
        Verification verification = new Verification();
        verification.setElectionEventId(ELECTION_EVENT_ID);
        verification.setId(1);
        verification.setSignedVerificationPublicKey(SIGNED_VERIFICATION_PUBLIC_KEY);
        verification.setTenantId(TENANT_ID);
        verification.setVerificationCardId(VERIFICATION_CARD_ID);
        verification.setVerificationCardKeystore(VERIFICATION_CARD_KEYSTORE);
        verification.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);

        return verification;
    }

    private Vote prepareMockVote() throws GeneralCryptoLibException {
        List<Exponent> exponentList = new ArrayList<Exponent>();
        exponentList.add(new Exponent(new BigInteger("3"), new BigInteger("2")));

        Proof proofMock = new Proof(new Exponent(new BigInteger("3"), new BigInteger("2")), exponentList);

        Vote vote = new Vote();
        vote.setAuthenticationToken(AUTHENTICATION_TOKEN);
        vote.setCipherTextExponentiations(CIPHER_TEXT_EXPONENTIATIONS);
        vote.setCorrectnessIds(CORRECTNESS_IDS);
        vote.setElectionEventId(ELECTION_EVENT_ID);
        vote.setEncryptedOptions(ENCRYPTED_OPTIONS);
        vote.setEncryptedWriteIns(ENCRYPTES_WRITE_INS);
        vote.setEncryptedPartialChoiceCodes(ENCRYPTED_PARTIAL_CHOICE_CODES);
        vote.setExponentiationProof(EXPONENTIATION_PROOF);
        vote.setPlaintextEqualityProof(proofMock.toJson());
        vote.setSchnorrProof(SCHNORR_PROOF);
        vote.setTenantId(TENANT_ID);
        vote.setVerificationCardId(VERIFICATION_CARD_ID);
        vote.setVerificationCardPKSignature(VERIFICATION_CARD_PK_SIGNATURE);
        vote.setVerificationCardPublicKey(VERIFICATION_CARD_PUBLIC_KEY);
        vote.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);
        vote.setVotingCardId(VOTING_CARD_ID);

        return vote;
    }

}
