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

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;

import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.util.Base64;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.commons.beans.domain.model.vote.Vote;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.ballot.Ballot;
import com.scytl.products.ov.ei.services.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformation;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformationRepository;

@RunWith(MockitoJUnitRunner.class)
public class WriteInsRuleTest {

    @Mock
    Logger LOG;

    @Mock
    BallotRepository ballotRepository;

    @Mock
    BallotBoxInformationRepository ballotBoxInformationRepository;

    @Rule
    public ExpectedException expected = ExpectedException.none();

    @InjectMocks
    public WriteinsRule rule = new WriteinsRule();
    
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this.getClass());
    }

    @Test
    public void testValid() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "47947432462544277669119938316244488177810433985361920221140083082656439983833;47947432462544277669119938316244488177810433985361920221140083082656439983833";
        Vote vote = prepareMockData(encryptedWriteIns, 2);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.SUCCESS));
    }

    @Test
    public void testValidOneWriteIn() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "47947432462544277669119938316244488177810433985361920221140083082656439983833";
        Vote vote = prepareMockData(encryptedWriteIns, 1);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.SUCCESS));
    }

    @Test
    public void testWithoutWriteIn1() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "";
        Vote vote = prepareMockData(encryptedWriteIns, 0);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.SUCCESS));
    }

    @Test
    public void testWithoutWriteIn2() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = null;
        Vote vote = prepareMockData(encryptedWriteIns, 0);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.SUCCESS));
    }

    @Test
    public void testPatternMismatch() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "test_os;test2";
        Vote vote = prepareMockData(encryptedWriteIns, 2);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.FAILED));
        assertEquals("The encrypted writeins didnt contain correctly encoded values", result.getErrorArgs()[0]);
    }

    @Test
    public void testInvalidGroup() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "03675137330;1000000000";
        Vote vote = prepareMockData(encryptedWriteIns, 2);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.FAILED));
        assertEquals("The encrypted writeins do not belong to the correct mathematical group", result.getErrorArgs()[0]);
    }

    @Test
    public void testTooManyWriteIns() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "231;152;234;345;45";
        Vote vote = prepareMockData(encryptedWriteIns, 2);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.FAILED));
        assertEquals("The encrypted writeins didnt contain the expected number of elements", result.getErrorArgs()[0]);
    }

    @Test
    public void testNotEnoughWriteIns() throws GeneralCryptoLibException, CertificateException, ResourceNotFoundException {
        String encryptedWriteIns = "231";
        Vote vote = prepareMockData(encryptedWriteIns, 2);
        
        ValidationError result = rule.execute(vote);
        assertTrue(result.getValidationErrorType().equals(ValidationErrorType.FAILED));
        assertEquals("The encrypted writeins didnt contain the expected number of elements", result.getErrorArgs()[0]);
    }

	private Vote prepareMockData(String encryptedWriteIns, int allowedWriteIns) throws ResourceNotFoundException {
		Vote vote = new Vote();
        vote.setTenantId("100");
        vote.setElectionEventId("f86b9967f5f14a01b9f70c6277c38329");
        vote.setVotingCardId("14d96e761e571a08769dc4337a2ce98b");
        vote.setBallotBoxId("b972e500fe554ed7b24e54814e9b3c85");
        vote.setBallotId("0edd4256df7d4b17a148f218080abea9");
        vote.setCredentialId("0edc375a02584cb09f35a9b956bc768f");
		vote.setEncryptedWriteIns(encryptedWriteIns);
        vote.setSignature(Base64.getEncoder().encodeToString("signature".getBytes(StandardCharsets.UTF_8)));
        vote.setCertificate("certificate");

        BallotBoxInformation ballotBox = new BallotBoxInformation();
        JsonObject ballotBoxInformation = new JsonObject();
        JsonObject encryptionParameters = new JsonObject();
        encryptionParameters.addProperty("p", "98410948467621226217939024562481745739029877894524580801342393875448933957899");
        encryptionParameters.addProperty("q", "49205474233810613108969512281240872869514938947262290400671196937724466978949");
        encryptionParameters.addProperty("g", "3");
        ballotBoxInformation.add("encryptionParameters", encryptionParameters);
        ballotBox.setJson(ballotBoxInformation.toString());
		when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(any(), any(), any())).thenReturn(ballotBox);
		
		Ballot ballot = new Ballot();
        JsonObject ballotInformation = new JsonObject();
        JsonArray contests = new JsonArray();
        JsonObject contest = new JsonObject();
        JsonArray questions = new JsonArray();
        JsonObject question = new JsonObject();
        question.addProperty("writeIn", "true");
        question.addProperty("max", Integer.toString(allowedWriteIns));
        questions.add(question);
        JsonObject question2 = new JsonObject();
        question2.addProperty("writeIn", "false");
        question2.addProperty("max", Integer.toString(allowedWriteIns));
        questions.add(question2);
        contest.add("questions", questions);
        contests.add(contest);
        ballotInformation.add("contests", contests);
		ballot.setJson(ballotInformation.toString());
		when(ballotRepository.findByTenantIdElectionEventIdBallotId(any(), any(), any())).thenReturn(ballot);

		return vote;
	}
}
