/*
 * 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.service.ballotbox;

import static com.scytl.products.ov.ei.services.domain.model.util.Constants.BALLOT_BOX_ID;
import static com.scytl.products.ov.ei.services.domain.model.util.Constants.ELECTION_EVENT_ID;
import static com.scytl.products.ov.ei.services.domain.model.util.Constants.TENANT_ID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.eq;
import static java.util.Collections.singleton;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Map;

import javax.ejb.SessionContext;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.domain.model.EntityId;
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.crypto.PrivateKeyForObjectRepository;
import com.scytl.products.ov.commons.signature.SignatureFactory;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBox;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformation;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformationRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxStatus;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogConstants;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogEvents;

/**
 *
 */
@RunWith(MockitoJUnitRunner.class)
public class BallotBoxServiceTest {
    private static final String TRACK_ID = "trackId";

    private static final EntityId ENTITY_ID = new EntityId();
    static {
        ENTITY_ID.setId(BALLOT_BOX_ID);
    }

    @Mock
    private BallotBoxRepository ballotBoxRepository;

    @Mock
    private SignatureFactory signatureFactory;

    @Mock
    private BallotBoxInformationService ballotBoxInformationService;

    @Mock
    private BallotBoxInformationRepository ballotBoxInformationRepository;

    @Mock
    private TrackIdInstance trackId;

    @Mock
    private SecureLoggingWriter secureLogger;

    @Mock
    private Logger logger;

    @Mock
    private PrivateKeyForObjectRepository privateKeyRepository;

    @Mock
    private SessionContext context;

    @InjectMocks
    private static BallotBoxService ballotBoxService = new BallotBoxServiceImpl();

    @Before
    public void setUp() {
        when(trackId.getTrackId()).thenReturn(TRACK_ID);
    }

    @Test
    public void emptyBallotBoxes() {
        when(ballotBoxRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(new ArrayList<BallotBox>());

        String tenantId = "1";
        String electionEventId = "2";
        String ballotBoxId = "3";
        assertTrue(ballotBoxService.checkIfBallotBoxesAreEmpty(tenantId, electionEventId, ballotBoxId).isResult());
    }

    @Test
    public void notEmptyBallotBoxes() {
        ArrayList<BallotBox> result = new ArrayList<BallotBox>();
        result.add(new BallotBox());
        when(ballotBoxRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(), anyString()))
            .thenReturn(result);

        String tenantId = "1";
        String electionEventId = "2";
        String ballotBoxId = "3";
        assertFalse(ballotBoxService.checkIfBallotBoxesAreEmpty(tenantId, electionEventId, ballotBoxId).isResult());
        assertTrue(ballotBoxService.checkIfBallotBoxesAreEmpty(tenantId, electionEventId, ballotBoxId)
            .getValidationError().getValidationErrorType().equals(ValidationErrorType.FAILED));
    }

    @Test
    public void blockBallotBoxes() throws ResourceNotFoundException, EntryPersistenceException {
        ballotBoxService.blockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        verify(ballotBoxInformationService).changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID,
            BallotBoxStatus.BLOCKED);
        verify(secureLogger).log(eq(Level.INFO), argThat(new ArgumentMatcher<LogContent>() {

            @Override
            public boolean matches(final Object argument) {
                LogContent content = (LogContent) argument;
                assertEquals(ElectionInformationLogEvents.BLOCK_BALLOT_BOX_OK, content.getLogEvent());
                assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                assertEquals(BALLOT_BOX_ID, content.getObjectId());
                Map<String, String> info = content.getAdditionalInfo();
                assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                return true;
            }
        }));
    }

    @Test(expected = ResourceNotFoundException.class)
    public void blockBallotBoxesNotFound() throws ResourceNotFoundException, EntryPersistenceException {
        doThrow(new ResourceNotFoundException("test")).when(ballotBoxInformationService)
            .changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID, BallotBoxStatus.BLOCKED);
        try {
            ballotBoxService.blockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        } catch (ResourceNotFoundException e) {
            verify(context).setRollbackOnly();
            verify(secureLogger).log(eq(Level.WARN), argThat(new ArgumentMatcher<LogContent>() {

                @Override
                public boolean matches(final Object argument) {
                    LogContent content = (LogContent) argument;
                    assertEquals(ElectionInformationLogEvents.BALLOT_BOX_TO_BLOCK_NOT_FOUND, content.getLogEvent());
                    assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                    assertEquals(BALLOT_BOX_ID, content.getObjectId());
                    Map<String, String> info = content.getAdditionalInfo();
                    assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                    assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                    return true;
                }
            }));
            throw e;
        }
    }

    @Test(expected = EntryPersistenceException.class)
    public void blockBallotBoxesError() throws ResourceNotFoundException, EntryPersistenceException {
        doThrow(new EntryPersistenceException("test")).when(ballotBoxInformationService)
            .changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID, BallotBoxStatus.BLOCKED);
        try {
            ballotBoxService.blockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        } catch (EntryPersistenceException e) {
            verify(context).setRollbackOnly();
            verify(secureLogger).log(eq(Level.ERROR), argThat(new ArgumentMatcher<LogContent>() {

                @Override
                public boolean matches(final Object argument) {
                    LogContent content = (LogContent) argument;
                    assertEquals(ElectionInformationLogEvents.ERROR_BLOCKING_BALLOT_BOX, content.getLogEvent());
                    assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                    assertEquals(BALLOT_BOX_ID, content.getObjectId());
                    Map<String, String> info = content.getAdditionalInfo();
                    assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                    assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                    return true;
                }
            }));
            throw e;
        }
    }

    @Test
    public void unblockBallotBoxes() throws ResourceNotFoundException, EntryPersistenceException {
        ballotBoxService.unblockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        verify(ballotBoxInformationService).changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID,
            BallotBoxStatus.NONE);
        verify(secureLogger).log(eq(Level.INFO), argThat(new ArgumentMatcher<LogContent>() {

            @Override
            public boolean matches(final Object argument) {
                LogContent content = (LogContent) argument;
                assertEquals(ElectionInformationLogEvents.UNBLOCK_BALLOT_BOX_OK, content.getLogEvent());
                assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                assertEquals(BALLOT_BOX_ID, content.getObjectId());
                Map<String, String> info = content.getAdditionalInfo();
                assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                return true;
            }
        }));
    }

    @Test(expected = ResourceNotFoundException.class)
    public void unblockBallotBoxesNotFound() throws ResourceNotFoundException, EntryPersistenceException {
        doThrow(new ResourceNotFoundException("test")).when(ballotBoxInformationService)
            .changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID, BallotBoxStatus.NONE);
        try {
            ballotBoxService.unblockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        } catch (ResourceNotFoundException e) {
            verify(context).setRollbackOnly();
            verify(secureLogger).log(eq(Level.WARN), argThat(new ArgumentMatcher<LogContent>() {

                @Override
                public boolean matches(final Object argument) {
                    LogContent content = (LogContent) argument;
                    assertEquals(ElectionInformationLogEvents.BALLOT_BOX_TO_UNBLOCK_NOT_FOUND, content.getLogEvent());
                    assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                    assertEquals(BALLOT_BOX_ID, content.getObjectId());
                    Map<String, String> info = content.getAdditionalInfo();
                    assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                    assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                    return true;
                }
            }));
            throw e;
        }
    }

    @Test(expected = EntryPersistenceException.class)
    public void unblockBallotBoxesError() throws ResourceNotFoundException, EntryPersistenceException {
        doThrow(new EntryPersistenceException("test")).when(ballotBoxInformationService)
            .changeBallotBoxStatus(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID, BallotBoxStatus.NONE);
        try {
            ballotBoxService.unblockBallotBoxes(TENANT_ID, ELECTION_EVENT_ID, singleton(ENTITY_ID));
        } catch (EntryPersistenceException e) {
            verify(context).setRollbackOnly();
            verify(secureLogger).log(eq(Level.ERROR), argThat(new ArgumentMatcher<LogContent>() {

                @Override
                public boolean matches(final Object argument) {
                    LogContent content = (LogContent) argument;
                    assertEquals(ElectionInformationLogEvents.ERROR_UNBLOCKING_BALLOT_BOX, content.getLogEvent());
                    assertEquals(ELECTION_EVENT_ID, content.getElectionEvent());
                    assertEquals(BALLOT_BOX_ID, content.getObjectId());
                    Map<String, String> info = content.getAdditionalInfo();
                    assertEquals(TRACK_ID, info.get(ElectionInformationLogConstants.INFO_TRACK_ID));
                    assertEquals(BALLOT_BOX_ID, info.get(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID));
                    return true;
                }
            }));
            throw e;
        }
    }

    @Test
    public void isBlockedBallotBox() throws ResourceNotFoundException {

        BallotBoxInformation mock = mock(BallotBoxInformation.class);
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(mock);
        when(mock.getStatus()).thenReturn(BallotBoxStatus.BLOCKED);
        assertTrue(ballotBoxService.isBlockedBallotBox(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID));
    }

    @Test
    public void checkIfTest() throws ResourceNotFoundException {

        when(ballotBoxInformationService.isBallotBoxForTest(anyString(), anyString(), anyString())).thenReturn(true);
        assertTrue(ballotBoxService.checkIfTest(TENANT_ID, ELECTION_EVENT_ID, BALLOT_BOX_ID));
    }
}
