/*
 * 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.vw.services.domain.service;

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.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import javax.persistence.PessimisticLockException;
import java.util.Optional;

import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
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.exceptions.VoteCastCodeRepositoryException;
import com.scytl.products.ov.commons.beans.exceptions.VoteRepositoryException;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.vw.services.domain.model.ballotbox.BallotBoxInformationRepository;
import com.scytl.products.ov.vw.services.domain.model.confirmation.VoteCastCodeRepository;
import com.scytl.products.ov.vw.services.domain.model.information.VoterInformation;
import com.scytl.products.ov.vw.services.domain.model.information.VoterInformationRepository;
import com.scytl.products.ov.vw.services.domain.model.state.VotingCardState;
import com.scytl.products.ov.vw.services.domain.model.state.VotingCardStateRepository;
import com.scytl.products.ov.vw.services.domain.model.state.VotingCardStates;
import com.scytl.products.ov.vw.services.domain.model.vote.VoteRepository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class VotingCardStateServiceTest {

	@InjectMocks
	VotingCardStateService votingCardStateService = new VotingCardStateServiceImpl();

	@Mock
	VotingCardStateRepository votingCardRepositoryMock;

	@Mock
	VoteCastCodeRepository voteCastCodeRepositoryMock;

    @Mock
    BallotBoxInformationRepository ballotBoxInformationRepository;

    @Mock
    VoterInformationRepository voterInformationRepository;


	@Mock
	VoteRepository voteRepository;

	@Mock
	Logger logger;

	@Mock
	TrackIdInstance trackIdInstance;

	@Mock
    SecureLoggingWriter secureLoggingWriter;

	@Mock
	private VotingCardState votingCardStateMock;

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

	@Before
	public void setup() {
		when(trackIdInstance.getTrackId()).thenReturn("");
	}

	@Test
	public void getVotingCardStateNullInputParameters() throws ApplicationException, ResourceNotFoundException {
		String tenantId = null;
		String electionEventId = "1";
		String votingCardId = "1";

		expectedException.expect(ApplicationException.class);

		votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
	}

	@Test
	public void getVotingCardStateEmptyInputParameters() throws ApplicationException, ResourceNotFoundException {
		String tenantId = "";
		String electionEventId = "1";
		String votingCardId = "1";

		expectedException.expect(ApplicationException.class);

		votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
	}

	@Test
	public void getVotingCardStateNotFound() throws ApplicationException, ResourceNotFoundException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.NOT_SENT);
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(Optional.empty());

		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertTrue(result.getState().equals(VotingCardStates.NONE));
	}

	@Test(expected=ApplicationException.class)
	public void getVotingCardStateLockingException() throws ApplicationException, ResourceNotFoundException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenThrow(new PessimisticLockException("exception"));

		votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
	}

	@Test
	public void getVotingCardState() throws ApplicationException, ResourceNotFoundException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardState = new VotingCardState();
		votingCardState.setTenantId(tenantId);
		votingCardState.setElectionEventId(electionEventId);
		votingCardState.setVotingCardId(votingCardId);
		votingCardState.setState(VotingCardStates.NOT_SENT);
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(Optional.of(votingCardState));

		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertNotNull(result);
	}

	@Test
	public void whenNotSentWithVoteShouldReturnSentNotCast()
			throws ApplicationException, ResourceNotFoundException, VoteCastCodeRepositoryException,
			VoteRepositoryException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		when(voteCastCodeRepositoryMock.voteCastCodeExists(anyString(),anyString(),anyString())).thenReturn(false);
		when(voteRepository.voteExists(anyString(),anyString(), anyString())).thenReturn(true);
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(notSent(tenantId, electionEventId, votingCardId));

		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertNotNull(result);
		assertEquals(VotingCardStates.SENT_BUT_NOT_CAST, result.getState());
	}

	@Test
	public void whenSentNotCastWithVoteShouldReturnSentNotCast()
			throws ApplicationException, ResourceNotFoundException, VoteCastCodeRepositoryException, VoteRepositoryException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		when(voteCastCodeRepositoryMock.voteCastCodeExists(anyString(),anyString(),anyString())).thenReturn(false);
		when(voteRepository.voteExists(anyString(),anyString(), anyString())).thenReturn(true);
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(sentNotCast(tenantId, electionEventId, votingCardId));


		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertNotNull(result);
		assertEquals(VotingCardStates.SENT_BUT_NOT_CAST, result.getState());
	}

	@Test
	public void whenNotSentWithVoteCastCodeShouldReturnCast()
			throws ApplicationException, ResourceNotFoundException, VoteCastCodeRepositoryException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		when(voteCastCodeRepositoryMock.voteCastCodeExists(anyString(),anyString(),anyString())).thenReturn(true);

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(notSent(tenantId, electionEventId, votingCardId));

		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertNotNull(result);
		assertEquals(VotingCardStates.CAST, result.getState());
	}

	@Test
	public void whenSentNotCastWithVoteCastCodeShouldReturnCast()
			throws ApplicationException, ResourceNotFoundException, VoteCastCodeRepositoryException {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		when(voteCastCodeRepositoryMock.voteCastCodeExists(anyString(),anyString(),anyString())).thenReturn(true);

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId))
			.thenReturn(sentNotCast(tenantId, electionEventId, votingCardId));

		VotingCardState result = votingCardStateService.getVotingCardState(tenantId, electionEventId, votingCardId);
		assertNotNull(result);
		assertEquals(VotingCardStates.CAST, result.getState());
	}

	@Test
	public void blockVotingCardAllowedWhenConfirmationRequiredAndStateIs_SentButNotCast() throws Exception {
        //given
	    String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

	    String ballotBoxWithConfirmationRequiredTrue = getBallotBox(true);
	    when(ballotBoxInformationRepository
			.getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
			.thenReturn(ballotBoxWithConfirmationRequiredTrue);

	    when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(anyString(),anyString(),anyString()))
            .thenReturn(new VoterInformation());

        doReturn(sentNotCast(tenantId, electionEventId, votingCardId))
            .when(votingCardRepositoryMock).acquire(anyString(), anyString(), anyString());

        //when
        votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

        //then verify that we called the save method, which means it was allowed to block
        Mockito.verify(votingCardRepositoryMock, times(1)).save(any());
    }

    @Test
    public void blockVotingCardNotAllowedWhenConfirmationRequiredAndStateIs_Cast() throws Exception {
        //given
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        String ballotBoxWithConfirmationRequiredTrue = getBallotBox(true);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredTrue);

        when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(anyString(),anyString(),anyString()))
            .thenReturn(new VoterInformation());

        doReturn(cast(tenantId, electionEventId, votingCardId))
            .when(votingCardRepositoryMock).acquire(anyString(), anyString(), anyString());

        //when
        votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

        //then verify that we DID NOT call the save method, which means it was NOT allowed to block
        Mockito.verify(votingCardRepositoryMock, times(0)).save(any());
    }


    @Test
	public void blockVotingCardAllowedWhenConfirmationNotRequiredAndStateIsNot_SentButNotCast() throws Exception {
        //given
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);

        when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(anyString(),anyString(),anyString()))
            .thenReturn(new VoterInformation());

        doReturn(notSent(tenantId, electionEventId, votingCardId))
            .when(votingCardRepositoryMock).acquire(anyString(), anyString(), anyString());

        //when
        votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

        //then verify that we called the save method, which means it was allowed to block
        Mockito.verify(votingCardRepositoryMock, times(1)).save(any());
	}

    @Test
    public void blockVotingCardNotAllowedWhenConfirmationNotRequiredAndStateIs_SentButNotCast() throws Exception {
        //given
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);

        when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(anyString(),anyString(),anyString()))
            .thenReturn(new VoterInformation());

        doReturn(sentNotCast(tenantId, electionEventId, votingCardId))
            .when(votingCardRepositoryMock).acquire(anyString(), anyString(), anyString());

        //when
        votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

        //then verify that we DID NOT call the save method, which means it was NOT allowed to block
        Mockito.verify(votingCardRepositoryMock, times(0)).save(any());
    }

	@Test
	public void testUpdateAttemptsSuccessful() throws ResourceNotFoundException, ApplicationException, DuplicateEntryException	{
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.CAST);
		votingCardStateMock.setAttempts(0);

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.of(votingCardStateMock));

		votingCardStateService.incrementVotingCardAttempts(tenantId, electionEventId, votingCardId);
		assertEquals(1, votingCardStateMock.getAttempts());
	}

	@Test
	public void testBlockVotingCardIgnoreUnableSuccessful() throws Exception {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.NOT_SENT);
		votingCardStateMock.setAttempts(0);

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);
		when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(tenantId, electionEventId, votingCardId)).thenReturn(new VoterInformation());
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.of(votingCardStateMock));

		votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

		assertEquals(VotingCardStates.BLOCKED, votingCardStateMock.getState());
	}

	@Test
	public void testBlockVotingCardIgnoreUnableNotPresentSuccessful() throws Exception {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);
		when(voterInformationRepository
            .getByTenantIdElectionEventIdVotingCardId(tenantId, electionEventId, votingCardId))
            .thenReturn(new VoterInformation());
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.empty());

		votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);
	}

	@Test
	public void testBlockVotingCardIgnoreUnableShouldNotBlock() throws Exception {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.CAST);
		votingCardStateMock.setAttempts(0);

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);
		when(voterInformationRepository.getByTenantIdElectionEventIdVotingCardId(tenantId, electionEventId, votingCardId)).thenReturn(new VoterInformation());
		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.of(votingCardStateMock));

		votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);

		assertEquals(VotingCardStates.CAST, votingCardStateMock.getState());
	}

	@Test
	public void testBlockVotingCardIgnoreUnableNotFound() throws Exception {
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

        VotingCardState votingCardStateMock = new VotingCardState();
        votingCardStateMock.setTenantId(tenantId);
        votingCardStateMock.setElectionEventId(electionEventId);
        votingCardStateMock.setVotingCardId(votingCardId);
        votingCardStateMock.setState(VotingCardStates.CAST);
        votingCardStateMock.setAttempts(0);

        String ballotBoxWithConfirmationRequiredFalse = getBallotBox(false);
        when(ballotBoxInformationRepository
            .getBallotBoxInfoByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(ballotBoxWithConfirmationRequiredFalse);
		when(voterInformationRepository
            .getByTenantIdElectionEventIdVotingCardId(tenantId, electionEventId, votingCardId))
            .thenReturn(new VoterInformation());
        when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.of(votingCardStateMock));

		votingCardStateService.blockVotingCardIgnoreUnable(tenantId, electionEventId, votingCardId);
	}

	@Test
	public void testUpdateVotingCardStateSuccessful() throws ResourceNotFoundException, ApplicationException, DuplicateEntryException
	{
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.CAST);

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.of(votingCardStateMock));

		votingCardStateService.updateVotingCardState(tenantId, electionEventId, votingCardId, VotingCardStates.SENT_BUT_NOT_CAST);

		assertEquals(VotingCardStates.SENT_BUT_NOT_CAST, votingCardStateMock.getState());
	}

	@Test
	public void testInitializeVotingCardStateSuccessful() throws DuplicateEntryException, EntryPersistenceException, ResourceNotFoundException
	{
		String tenantId = "1";
		String electionEventId = "1";
		String votingCardId = "1";

		VotingCardState votingCardStateMock = new VotingCardState();
		votingCardStateMock.setTenantId(tenantId);
		votingCardStateMock.setElectionEventId(electionEventId);
		votingCardStateMock.setVotingCardId(votingCardId);
		votingCardStateMock.setState(VotingCardStates.NONE);

		when(votingCardRepositoryMock.acquire(tenantId, electionEventId, votingCardId)).thenReturn(Optional.empty());

		votingCardStateService.initializeVotingCardState(votingCardStateMock);

		assertEquals(VotingCardStates.NOT_SENT, votingCardStateMock.getState());
	}

	private Optional<VotingCardState> notSent(String tenantId, String electionEventId, String votingCardId) {
		VotingCardState state = new VotingCardState();
		state.setTenantId(tenantId);
		state.setElectionEventId(electionEventId);
		state.setVotingCardId(votingCardId);
		state.setState(VotingCardStates.NOT_SENT);
		return Optional.of(state);
	}

	private Optional<VotingCardState> sentNotCast(String tenantId, String electionEventId, String votingCardId) {
		VotingCardState state = new VotingCardState();
		state.setTenantId(tenantId);
		state.setElectionEventId(electionEventId);
		state.setVotingCardId(votingCardId);
		state.setState(VotingCardStates.SENT_BUT_NOT_CAST);
		return Optional.of(state);
	}

    private Optional<VotingCardState> cast(final String tenantId, final String electionEventId, final String votingCardId) {
        VotingCardState state = new VotingCardState();
        state.setTenantId(tenantId);
        state.setElectionEventId(electionEventId);
        state.setVotingCardId(votingCardId);
        state.setState(VotingCardStates.CAST);
        return Optional.of(state);
    }

    private String getBallotBox(final boolean confirmationRequired) throws Exception {
        return "{\"confirmationRequired\":" + confirmationRequired + "}";
    }
}
