/*
 * 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;

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

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

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

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.cms.CMSService;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
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.validation.ElectionValidationRequest;
import com.scytl.products.ov.ei.services.domain.service.election.ElectionService;
import com.scytl.products.ov.ei.services.domain.service.election.ElectionServiceImpl;

@RunWith(MockitoJUnitRunner.class)
public class ElectionServiceTest {

    public static final long SECONDS_30 = 30L;

    public static final String ZERO = "0";

    @Mock
    private BallotBoxInformationRepository ballotBoxInformationRepository;

    @Mock
    private TrackIdInstance trackId;

    @Mock
    private SecureLoggingWriter secureLoggingWriter;

    @Mock
    private CMSService cmsService;

    @InjectMocks
    private ElectionService electionService = new ElectionServiceImpl();

    private static Long days_4 = 4L;

    private static Long days_8 = 8L;

    private static long minutes_1 = 1L;

    private static LocalDateTime now;

    private DateTimeFormatter formatter;

    public static String seconds_600 = "600";

    @Before
    public void setup() {
        formatter = DateTimeFormatter.ISO_DATE_TIME;
        now = LocalDateTime.now();
        when(trackId.getTrackId()).thenReturn("");
    }

    @Test
    public void electionInDatesTest() throws ResourceNotFoundException {
        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", now.minusDays(days_4).format(formatter))
            .add("endDate", now.plusDays(days_4).format(formatter)).add("gracePeriod", seconds_600);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertTrue(electionService.validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false))
            .getValidationErrorType().equals(ValidationErrorType.SUCCESS));
    }

    @Test
    public void electionBeforeDateFrom() throws ResourceNotFoundException, GeneralCryptoLibException {
        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", now.plusDays(days_4).format(formatter))
            .add("endDate", now.plusDays(days_8).format(formatter)).add("gracePeriod", seconds_600);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertTrue(electionService.validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false))
            .getValidationErrorType().equals(ValidationErrorType.ELECTION_NOT_STARTED));
    }

    @Test
    public void electionAfterDateTo() throws ResourceNotFoundException {
        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", now.minusDays(days_8).format(formatter))
            .add("endDate", now.minusDays(days_4).format(formatter)).add("gracePeriod", seconds_600);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);

        assertTrue(electionService.validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false))
            .getValidationErrorType().equals(ValidationErrorType.ELECTION_OVER_DATE));
    }

    @Test
    public void testGracePeriodOK() throws ResourceNotFoundException {
        ZonedDateTime dateTime = ZonedDateTime.now(ZoneOffset.UTC);
        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", dateTime.minusMinutes(minutes_1).format(formatter))
            .add("endDate", dateTime.minusSeconds(SECONDS_30).format(formatter)).add("gracePeriod", seconds_600);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertTrue(electionService.validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", true))
            .getValidationErrorType().equals(ValidationErrorType.SUCCESS));

    }

    @Test
    public void testGracePeriodKO() throws ResourceNotFoundException {
        ZonedDateTime dateTime = ZonedDateTime.now(ZoneOffset.UTC);
        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", dateTime.minusMinutes(minutes_1).format(formatter))
            .add("endDate", dateTime.minusSeconds(SECONDS_30).format(formatter)).add("gracePeriod", ZERO);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertTrue(electionService.validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false))
            .getValidationErrorType().equals(ValidationErrorType.ELECTION_OVER_DATE));
    }

    @Test
    public void test_when_election_valid_should_not_log_anything() throws Exception {
        ZonedDateTime start = ZonedDateTime.now(ZoneOffset.UTC);
        ZonedDateTime end = start.plus(1, ChronoUnit.YEARS);

        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", start.format(formatter)).add("endDate", end.format(formatter)).add("gracePeriod",
            ZERO);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertEquals(ValidationErrorType.SUCCESS, electionService
            .validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false)).getValidationErrorType());

        // check that secure logger is not called
        Mockito.verifyZeroInteractions(secureLoggingWriter);
    }

    @Test
    public void test_when_election_closed_invalid_should_log_event() throws Exception {
        // election closed 1 minute ago
        ZonedDateTime start = ZonedDateTime.now(ZoneOffset.UTC).minus(10, ChronoUnit.MINUTES);
        ZonedDateTime end = start.minus(1, ChronoUnit.MINUTES);

        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", start.format(formatter)).add("endDate", end.format(formatter)).add("gracePeriod",
            ZERO);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertEquals(ValidationErrorType.ELECTION_OVER_DATE, electionService
            .validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false)).getValidationErrorType());

        // check that secure logger is called once
        Mockito.verify(secureLoggingWriter, times(1)).log(any(), any());
    }

    @Test
    public void test_when_election_not_started_should_log_event() throws Exception {
        // election starting 10 minutes from now
        ZonedDateTime start = ZonedDateTime.now(ZoneOffset.UTC).plus(10, ChronoUnit.MINUTES);
        ZonedDateTime end = start.plus(10, ChronoUnit.MINUTES);

        JsonObjectBuilder jsonObject = Json.createObjectBuilder();
        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        jsonObject.add("startDate", start.format(formatter)).add("endDate", end.format(formatter)).add("gracePeriod",
            ZERO);
        ballotBoxInformation.setJson(jsonObject.build().toString());
        when(ballotBoxInformationRepository.findByTenantIdElectionEventIdBallotBoxId(anyString(), anyString(),
            anyString())).thenReturn(ballotBoxInformation);
        assertEquals(ValidationErrorType.ELECTION_NOT_STARTED, electionService
            .validateIfElectionIsOpen(ElectionValidationRequest.create("1", "1", "1", false)).getValidationErrorType());

        // check that secure logger is called once
        Mockito.verify(secureLoggingWriter, times(1)).log(any(), any());
    }
}
