/*
 * 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.config.commands;

import static com.scytl.products.ov.config.commons.Constants.JOB_IN_PARAM_ELECTIONEVENT_ID;
import static com.scytl.products.ov.config.commons.Constants.JOB_IN_PARAM_JOB_INSTANCE_ID;
import static com.scytl.products.ov.config.commons.Constants.JOB_IN_PARAM_TENANT_ID;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.explore.JobExplorer;

import com.scytl.products.ov.config.commands.api.ConfigurationService;
import com.scytl.products.ov.config.commands.api.output.BallotBoxesServiceOutput;
import com.scytl.products.ov.config.commands.api.output.ElectionEventServiceOutput;
import com.scytl.products.ov.config.commands.ballotbox.BallotBoxGenerator;
import com.scytl.products.ov.config.commands.ballotbox.BallotBoxHolderInitializer;
import com.scytl.products.ov.config.commands.ballotbox.BallotBoxParametersHolder;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventGenerator;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventHolderInitializer;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventOutput;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventParametersHolder;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventSerializer;
import com.scytl.products.ov.config.commands.progress.ProgressManager;
import com.scytl.products.ov.config.commons.beans.VotingCardGenerationJobStatus;

@RunWith(MockitoJUnitRunner.class)
public class ConfigurationServiceTest {

    @Mock
    CreateElectionEventHolderInitializer eventHolderInitializer;

    @Mock
    CreateElectionEventGenerator electionEventGenerator;

    @Mock
    CreateElectionEventSerializer electionEventSerializer;

    @Mock
    BallotBoxHolderInitializer ballotBoxHolderInitializer;

    @Mock
    BallotBoxGenerator ballotBoxGenerator;

    @InjectMocks
    private ConfigurationService sut = new ConfigurationService();

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Test
    public void generateCorrectOutputFoldersFromInputParametersWhenCreatingElectionEvent() throws Exception {
        // given
        CreateElectionEventParametersHolder inputParameters = getCreateElectionEventInputParameters();

        CreateElectionEventOutput mockOutput = mock(CreateElectionEventOutput.class);
        doAnswer(invocationOnMock -> null).when(mockOutput).clearPasswords();

        doAnswer(invocationOnMock -> null).when(eventHolderInitializer).init(any(), any(File.class));
        when(electionEventGenerator.generate(any())).thenReturn(mockOutput);
        doAnswer(invocationOnMock -> null).when(electionEventSerializer).serialize(any(), any());

        // when
        final ElectionEventServiceOutput output = sut.createElectionEvent(inputParameters);

        // then
        Assert.assertEquals(output.getOfflineFolder(), inputParameters.getOfflineFolder().toString());
        Assert.assertEquals(output.getOnlineAuthenticationFolder(),
            inputParameters.getOnlineAuthenticationFolder().toString());
        Assert.assertEquals(output.getOnlineElectionInformationFolder(),
            inputParameters.getOnlineElectionInformationFolder().toString());
    }

    @Test
    public void generateCorrectOutputFromInputParametersWhenCreatingBallotBoxes() throws Exception {
        // given
        BallotBoxParametersHolder inputParameters = getCreateBallotBoxesInputParameters();
        String generatedBallotBoxName = temporaryFolder.newFile().getName();
        Path fullBallotBoxPath = Paths.get(inputParameters.getOutputPath().toString(), generatedBallotBoxName);

        BallotBoxesServiceOutput mockOutput = mock(BallotBoxesServiceOutput.class);
        when(mockOutput.getOutputPath()).thenReturn(fullBallotBoxPath.toString());
        doAnswer(invocationOnMock -> null).when(ballotBoxHolderInitializer).init(any(), any(File.class));
        when(ballotBoxGenerator.generate(any())).thenReturn(mockOutput);

        // when
        final BallotBoxesServiceOutput output = sut.createBallotBoxes(inputParameters);

        // then
        Assert.assertEquals(output.getOutputPath(), fullBallotBoxPath.toString());
    }

    @Mock
    JobExplorer fakeJobExplorer;

    @Mock
    ProgressManager progressManager;

    @Test
    public void returnEmptyWhenAskingForJobStatusThatDoesNotExist() throws Exception {

        // given
        when(fakeJobExplorer.findJobInstancesByJobName(anyString(), anyInt(), anyInt()))
            .thenReturn(Collections.emptyList());

        // when
        final Optional<VotingCardGenerationJobStatus> status =
            sut.getVotingCardGenerationJobStatus("fake", "fake", UUID.randomUUID().toString());

        // then
        Assert.assertFalse(status.isPresent());
    }

    @Test
    public void returnJobStatusAsStartingWhenJobIsCreated() throws Exception {

        // given
        String tenantId = "fake";
        String electionEventid = "fake";
        UUID jobId = UUID.randomUUID();

        List<JobInstance> fakeJobInstances = new ArrayList<>();
        JobInstance fakeJobInstance = new JobInstance(1L, "fakeJob");
        fakeJobInstances.add(fakeJobInstance);

        JobParameters fakeJobParameters = getFakeJobParameters(tenantId, electionEventid, jobId.toString());
        JobExecution fakeJobExecution = new JobExecution(fakeJobInstance, 1L, fakeJobParameters, null);

        List<JobExecution> fakeJobExecutions = new ArrayList<>();
        fakeJobExecutions.add(fakeJobExecution);

        when(fakeJobExplorer.findJobInstancesByJobName(anyString(), anyInt(), anyInt())).thenReturn(fakeJobInstances);
        when(fakeJobExplorer.getJobExecutions(any())).thenReturn(fakeJobExecutions);
        when(progressManager.getJobProgress(any())).thenReturn(Optional.empty());

        // when

        final Optional<VotingCardGenerationJobStatus> status =
            sut.getVotingCardGenerationJobStatus(tenantId, electionEventid, jobId.toString());

        // then
        Assert.assertTrue(status.isPresent());
        VotingCardGenerationJobStatus jobStatus = status.get();
        Assert.assertEquals(jobId, jobStatus.getJobId());
        Assert.assertEquals(BatchStatus.STARTING.toString(), jobStatus.getStatus());
        Assert.assertEquals(0, jobStatus.getErrorCount());
        Assert.assertEquals(0, jobStatus.getGeneratedCount());
        Assert.assertNull(jobStatus.getProgressDetails());
    }

    @Test
    public void returnListOfJobStatusAsStartingWhenJobsAreCreated() throws Exception {

        // given
        String tenantId = "fake";
        String electionEventid = "fake";
        UUID jobId = UUID.randomUUID();

        List<JobInstance> fakeJobInstances = new ArrayList<>();
        JobInstance fakeJobInstance = new JobInstance(1L, "fakeJob");
        fakeJobInstances.add(fakeJobInstance);

        JobParameters fakeJobParameters = getFakeJobParameters(tenantId, electionEventid, jobId.toString());
        JobExecution fakeJobExecution = new JobExecution(fakeJobInstance, 1L, fakeJobParameters, null);

        List<JobExecution> fakeJobExecutions = new ArrayList<>();
        fakeJobExecutions.add(fakeJobExecution);

        when(fakeJobExplorer.findJobInstancesByJobName(anyString(), anyInt(), anyInt())).thenReturn(fakeJobInstances);
        when(fakeJobExplorer.getJobExecutions(any())).thenReturn(fakeJobExecutions);
        when(progressManager.getJobProgress(any())).thenReturn(Optional.empty());

        // when

        final List<VotingCardGenerationJobStatus> jobs = sut.getJobs();

        // then
        Assert.assertTrue(jobs.size() > 0);
        jobs.forEach(j -> {
            Assert.assertEquals(jobId, j.getJobId());
            Assert.assertEquals(BatchStatus.STARTING.toString(), j.getStatus());
            Assert.assertEquals(0, j.getErrorCount());
            Assert.assertEquals(0, j.getGeneratedCount());
            Assert.assertNull(j.getProgressDetails());
        });

    }

    @Test
    public void returnCorrectListOfJobStatusWhenFilteringByStatus() throws Exception {

        // given
        UUID jobId1 = UUID.randomUUID();
        UUID jobId2 = UUID.randomUUID();
        String tenantId = "fake";
        String electionEventid = "fake";

        List<JobInstance> fakeJobInstances = new ArrayList<>();
        JobInstance fakeJobInstance1 = new JobInstance(1L, "fakeJob1");
        JobInstance fakeJobInstance2 = new JobInstance(2L, "fakeJob2");
        fakeJobInstances.add(fakeJobInstance1);
        fakeJobInstances.add(fakeJobInstance2);

        JobParameters fakeJobParametersJob1 = getFakeJobParameters(tenantId, electionEventid, jobId1.toString());
        JobParameters fakeJobParametersJob2 = getFakeJobParameters(tenantId, electionEventid, jobId2.toString());

        JobExecution fakeJobExecution1 = new JobExecution(fakeJobInstance1, 1L, fakeJobParametersJob1, null);
        JobExecution fakeJobExecution2 = new JobExecution(fakeJobInstance2, 2L, fakeJobParametersJob2, null);
        fakeJobExecution2.setStatus(BatchStatus.COMPLETED);

        List<JobExecution> fakeJobExecutions1 = new ArrayList<>();
        fakeJobExecutions1.add(fakeJobExecution1);
        List<JobExecution> fakeJobExecutions2 = new ArrayList<>();
        fakeJobExecutions2.add(fakeJobExecution2);

        when(fakeJobExplorer.findJobInstancesByJobName(anyString(), anyInt(), anyInt())).thenReturn(fakeJobInstances);
        when(fakeJobExplorer.getJobExecutions(fakeJobInstance1)).thenReturn(fakeJobExecutions1);
        when(fakeJobExplorer.getJobExecutions(fakeJobInstance2)).thenReturn(fakeJobExecutions2);
        when(progressManager.getJobProgress(any())).thenReturn(Optional.empty());

        // when

        final List<VotingCardGenerationJobStatus> jobs =
            sut.getJobsWithStatus(tenantId, electionEventid, BatchStatus.COMPLETED.toString());

        // then
        Assert.assertEquals(1, jobs.size());
        jobs.forEach(j -> {
            Assert.assertEquals(jobId2, j.getJobId());
            Assert.assertEquals(BatchStatus.COMPLETED.toString(), j.getStatus());
            Assert.assertEquals(0, j.getErrorCount());
            Assert.assertEquals(0, j.getGeneratedCount());
            Assert.assertNull(j.getProgressDetails());
        });

    }

    private JobParameters getFakeJobParameters(final String tenantId, final String electionEventId,
            final String jobId) {
        JobParametersBuilder builder = new JobParametersBuilder();
        builder.addString(JOB_IN_PARAM_TENANT_ID, tenantId);
        builder.addString(JOB_IN_PARAM_ELECTIONEVENT_ID, electionEventId);
        builder.addString(JOB_IN_PARAM_JOB_INSTANCE_ID, jobId);
        return builder.toJobParameters();
    }

    private CreateElectionEventParametersHolder getCreateElectionEventInputParameters() throws Exception {
        Path outputFolder = temporaryFolder.newFolder().toPath();
        Path offlineFolder = outputFolder.resolve("OFFLINE");
        Path onlineAuthenticationFolder = outputFolder.resolve("ONLINE").resolve("authentication");
        Path onlineElectionInformationFolder = outputFolder.resolve("ONLINE").resolve("electionInformation");
        Path onlineVotingWorkflowFolder = outputFolder.resolve("ONLINE").resolve("votingWorkflow");

        CreateElectionEventParametersHolder params =
            new CreateElectionEventParametersHolder(null, outputFolder, null, offlineFolder, onlineAuthenticationFolder,
                onlineElectionInformationFolder, onlineVotingWorkflowFolder, null, null, null, null, null);
        return params;

    }

    private BallotBoxParametersHolder getCreateBallotBoxesInputParameters() throws Exception {

        Path outputFolder = temporaryFolder.newFolder().toPath();
        BallotBoxParametersHolder params =
            new BallotBoxParametersHolder("", "", "", "", outputFolder, "", null, null, null, null, null, null);

        return params;
    }
}
