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

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.when;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.ws.rs.core.MediaType;

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.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;

import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.infrastructure.clients.ElectionInformationClient;
import com.scytl.products.ov.sdm.infrastructure.clients.OrchestratorClient;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;

/**
 * Test different scenarios when downloading ballot box
 */
@RunWith(MockitoJUnitRunner.class)
public class BallotBoxDownloadServiceTest {

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

    @Mock
    private BallotBoxRepository ballotBoxRepository;

    @Mock
    private ConfigurationEntityStatusService configurationEntityStatusService;

    @Mock
    private PathResolver pathResolver;

    @Mock
    private SecureLoggingWriter secureLoggingWriter;

    @Mock
    private HashService hashService;

    @InjectMocks
    @Spy
    private final BallotBoxDownloadService ballotBoxDownloadService = new BallotBoxDownloadService();

    private final String ballotBoxId = "7674245a6382449fb1afedcce1810565";

    private final String ballotBoxJsonLocked = "{\"result\": [ { \"id\":\"" + ballotBoxId
        + "\", \"ballot\":{ \"id\":\"76c70f14cd424cff89a3bfc6d1b6e51c\"} ,\"status\":\"" + Status.LOCKED
        + "\", \"test\":\"" + Boolean.FALSE + "\", \"synchronized\":\"" + Boolean.FALSE + "\"} ]}";

    private final String ballotBoxJsonElectionClosed = "{\"result\": [ { \"id\":\"" + ballotBoxId
        + "\", \"ballot\":{ \"id\":\"76c70f14cd424cff89a3bfc6d1b6e51c\"} ,\"status\":\"" + Status.MIXED
        + "\", \"test\":\"" + Boolean.FALSE + "\", \"synchronized\":\"" + Boolean.TRUE + "\"} ]}";

    private final String testBallotBoxJson = "{\"result\": [ { \"id\":\"" + ballotBoxId
        + "\", \"ballot\":{ \"id\":\"76c70f14cd424cff89a3bfc6d1b6e51c\"} ,\"status\":\"" + Status.SIGNED
        + "\", \"test\":\"" + Boolean.TRUE + "\", \"synchronized\":\"" + Boolean.TRUE + "\"} ]}";

    private final String ballotBoxJsonEmpty = "{\"result\": []}";

    private final String electionEventId = "1";

    @Mock
    private OrchestratorClient orchestratorClientMock;

    @Mock
    private ElectionInformationClient electionInformationClientMock;

    private final String ballotBoxCsv = "10,10;100;100";

    @Mock
    private Path pathMock;

    @Before
    public void injectValueAnnotatedFields() {
        ReflectionTestUtils.setField(ballotBoxDownloadService, "tenantId", "tenant");
    }

    @Test
    public void whenDownloadEmptyBallotBoxThenException() throws BallotBoxDownloadException {
        when(ballotBoxRepository.list(any())).thenReturn(ballotBoxJsonEmpty);
        exception.expect(BallotBoxDownloadException.class);
        exception.expectMessage("Ballot boxes are empty");

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);
    }

    @Test
    public void whenDownloadBallotBoxWithIncorrectStatusThenException() throws BallotBoxDownloadException {
        when(ballotBoxRepository.list(any())).thenReturn(ballotBoxJsonLocked);
        exception.expect(BallotBoxDownloadException.class);
        exception.expectMessage("Ballot box can not be downloaded because its status is LOCKED");

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);
    }

    @Test
    public void whenDownloadBallotBoxThenGetResponseNotOKFromVP() throws Exception {
        when(ballotBoxRepository.list(any())).thenReturn(ballotBoxJsonElectionClosed);
        doReturn(orchestratorClientMock).when(ballotBoxDownloadService).getOrchestratorClient();
        doReturn(electionInformationClientMock).when(ballotBoxDownloadService).getElectionInformationClient();
        @SuppressWarnings("unchecked")
        Call<ResponseBody> callMock = Mockito.mock(Call.class);
        when(callMock.execute()).thenReturn(Response.error(500,
            ResponseBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_OCTET_STREAM), ballotBoxCsv)));
        doReturn(callMock).when(electionInformationClientMock).getRawBallotBox(anyString(), anyString(), anyString());
        exception.expect(BallotBoxDownloadException.class);
        exception.expectMessage(String.format("Failed to download encrypted BallotBox [electionEvent=%s, ballotBox=%s]",
            electionEventId, ballotBoxId));

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);
    }

    @Test
    public void whenDownloadClosedBallotBoxThenOk()
            throws IOException, BallotBoxDownloadException, HashServiceException {
        Path targetPath = Files.createTempDirectory("temp").resolve("downloadedBallotBox.csv");

        when(ballotBoxRepository.list(any())).thenReturn(ballotBoxJsonElectionClosed);
        doReturn(orchestratorClientMock).when(ballotBoxDownloadService).getOrchestratorClient();
        doReturn(electionInformationClientMock).when(ballotBoxDownloadService).getElectionInformationClient();
        @SuppressWarnings("unchecked")
        Call<ResponseBody> callMock = Mockito.mock(Call.class);
        when(callMock.execute()).thenReturn(Response
            .success(ResponseBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_OCTET_STREAM), ballotBoxCsv)));
        doReturn(callMock).when(electionInformationClientMock).getRawBallotBox(anyString(), anyString(), anyString());
        when(pathResolver.resolve(Matchers.<String> anyVararg())).thenReturn(targetPath);
        when(hashService.getB64Hash(any(byte[].class))).thenReturn("ballotBoxHash");

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);

        assertTrue(Files.exists(targetPath));
    }

    @Test
    public void whenDownloadingTestBallotBoxThenOk()
            throws IOException, BallotBoxDownloadException, HashServiceException {
        Path targetPath = Files.createTempDirectory("temp").resolve("downloadedBallotBox.csv");

        when(ballotBoxRepository.list(any())).thenReturn(testBallotBoxJson);
        doReturn(orchestratorClientMock).when(ballotBoxDownloadService).getOrchestratorClient();
        doReturn(electionInformationClientMock).when(ballotBoxDownloadService).getElectionInformationClient();
        @SuppressWarnings("unchecked")
        Call<ResponseBody> callMock = Mockito.mock(Call.class);
        when(callMock.execute()).thenReturn(Response
            .success(ResponseBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_OCTET_STREAM), ballotBoxCsv)));
        doReturn(callMock).when(electionInformationClientMock).getRawBallotBox(anyString(), anyString(), anyString());
        when(pathResolver.resolve(Matchers.<String> anyVararg())).thenReturn(targetPath);
        when(hashService.getB64Hash(any(byte[].class))).thenReturn("ballotBoxHash");

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);

        assertTrue(Files.exists(targetPath));
    }

    @Test
    public void whenDownloadingBallotBoxToWrongPathThenFailWriting() throws IOException, BallotBoxDownloadException {
        Path wrongPath = Paths.get("/wrongpath/downloadedBallotBox.csv");

        when(ballotBoxRepository.list(any())).thenReturn(ballotBoxJsonElectionClosed);
        doReturn(orchestratorClientMock).when(ballotBoxDownloadService).getOrchestratorClient();
        doReturn(electionInformationClientMock).when(ballotBoxDownloadService).getElectionInformationClient();
        @SuppressWarnings("unchecked")
        Call<ResponseBody> callMock = Mockito.mock(Call.class);
        when(callMock.execute()).thenReturn(Response
            .success(ResponseBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_OCTET_STREAM), ballotBoxCsv)));
        doReturn(callMock).when(electionInformationClientMock).getRawBallotBox(anyString(), anyString(), anyString());
        when(pathResolver.resolve(Matchers.<String> anyVararg())).thenReturn(wrongPath);
        exception.expect(BallotBoxDownloadException.class);
        exception.expectMessage(
            String.format("Failed to write downloaded BallotBox [electionEvent=%s, ballotBox=%s] to file.",
                electionEventId, ballotBoxId));

        ballotBoxDownloadService.download(electionEventId, ballotBoxId);
    }
}
