/*
 * 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.assertFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doReturn;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;

import javax.json.JsonObject;

import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
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.ResourceNotFoundException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.sign.MetadataFileSigner;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;

import mockit.MockUp;

public class MixDecValServiceTest {

    private static final BallotRepository ballotRepository = mock(BallotRepository.class);

    private static final BallotBoxRepository ballotBoxRepository = mock(BallotBoxRepository.class);

    private static String ballotJson;

    private static String ballotBoxJson;

    private static String electionEventJson;

    private static String electoralAuthorityJson;

    private static String applicationConfigurationJson;

    private static String platformRootPEM;
    
    private static String tenantCAPem;

    private final String electionEventId = "electionEventId";

    private final String ballotBoxId = "ballotBoxId";

    private final String signingKeyPEM = "signingKeyPEM";

    private final String ballotDecryptionKey = "ballotDecryptionKey";

    private final Path pathMock = mock(Path.class);

    private final File fileMock = mock(File.class);

    private final PrivateKey privateKeyMock = mock(PrivateKey.class);

    private MixDecValService sut;

    @BeforeClass
    public static void setUpSuite() throws IOException {
        ballotJson = readFileContent("src/test/resources/mixing/ballotForMixing.json");
        ballotBoxJson = readFileContent("src/test/resources/mixing/ballotBoxForMixing.json");
        electionEventJson = readFileContent("src/test/resources/mixing/electionEvent.json");
        electoralAuthorityJson = readFileContent("src/test/resources/mixing/electoralAuthority.json");
        applicationConfigurationJson = readFileContent("src/test/resources/mixing/applicationConfig.json");
        platformRootPEM = readFileContent("src/test/resources/mixing/platformRootCA.pem");
        tenantCAPem = readFileContent("src/test/resources/mixing/tenantCA.pem");
    }

    @Before
    public void setUpTest() throws NoSuchFileException, IOException, GeneralCryptoLibException, CertificateManagementException {
        ElectionEventRepository electionEventRepository = mock(ElectionEventRepository.class);
        PathResolver pathResolver = mock(PathResolver.class);
        ConfigurationEntityStatusService configurationEntityStatusService =
            mock(ConfigurationEntityStatusService.class);
        MetadataFileSigner metadataFileSigner = mock(MetadataFileSigner.class);
        SecureLoggingWriter secureLoggingWriter = mock(SecureLoggingWriter.class);

        PlatformRootCAService platformRootService = mock(PlatformRootCAService.class);
        doReturn((X509Certificate) PemUtils.certificateFromPem(platformRootPEM)).when(platformRootService).load();

        RootCAService tenantCAService = mock(RootCAService.class);
        when(tenantCAService.load()).thenReturn((X509Certificate) PemUtils.certificateFromPem(tenantCAPem));

        when(ballotRepository.find(anyString())).thenReturn(ballotJson);
        when(ballotBoxRepository.find(anyString())).thenReturn(ballotBoxJson);
        when(pathResolver.resolve(anyString())).thenReturn(pathMock);
        when(pathMock.resolve(anyString())).thenReturn(pathMock);
        when(pathMock.toAbsolutePath()).thenReturn(pathMock);
        when(pathMock.toFile()).thenReturn(fileMock);
        when(configurationEntityStatusService.update(anyString(), anyString(), any())).thenReturn("");
        when(electionEventRepository.find(anyString())).thenReturn(electionEventJson);

        sut = new MixDecValService(ballotRepository, ballotBoxRepository, electionEventRepository, pathResolver,
            configurationEntityStatusService, metadataFileSigner, platformRootService, secureLoggingWriter);
    }

    @Test
    public void mixDecryptValidate()
            throws IOException, GeneralCryptoLibException, ResourceNotFoundException, ApplicationException {

        mockUpSUT(true);
        // Assert that the method is able to run.
        sut.mixDecryptValidate(electionEventId, ballotBoxId, signingKeyPEM, ballotDecryptionKey);
    }

    @Test
    public void validResponseTest()
            throws ApplicationException, ResourceNotFoundException, GeneralCryptoLibException, IOException {

        mockUpSUT(true);

        Boolean result = sut.mixDecryptValidate(electionEventId, ballotBoxId, signingKeyPEM, ballotDecryptionKey);

        Assert.assertTrue(result);
    }

    @Test
    public void invalidResponseTest()
            throws ApplicationException, ResourceNotFoundException, GeneralCryptoLibException, IOException {

        mockUpSUT(false);

        Boolean result = sut.mixDecryptValidate(electionEventId, ballotBoxId, signingKeyPEM, ballotDecryptionKey);

        assertFalse(result);
    }

    @Test(expected = ResourceNotFoundException.class)
    public void invalidBallotBoxIdTest()
            throws ApplicationException, ResourceNotFoundException, GeneralCryptoLibException, IOException {

        when(ballotBoxRepository.find(anyString())).thenReturn("{}");

        mockUpSUT(true);

        Boolean result = sut.mixDecryptValidate(electionEventId, ballotBoxId, signingKeyPEM, ballotDecryptionKey);

        assertFalse(result);
    }

    @Test(expected = ApplicationException.class)
    public void invalidBallotBoxStatus()
            throws ApplicationException, ResourceNotFoundException, GeneralCryptoLibException, IOException {

        when(ballotBoxRepository.find(anyString())).thenReturn("{\"status\": \"WHATEVER\"}");

        mockUpSUT(true);

        Boolean result = sut.mixDecryptValidate(electionEventId, ballotBoxId, signingKeyPEM, ballotDecryptionKey);

        assertFalse(result);
    }

    private static String readFileContent(String pathToFile) throws IOException {
        return new String(Files.readAllBytes(Paths.get(pathToFile)), StandardCharsets.UTF_8);
    }

    private void mockUpSUT(boolean isValid) {

        new MockUp<MixDecValService>() {

            @mockit.Mock
            JsonObject readBallotBoxJsonObject(Path ballotBoxPath) throws IOException {
                return JsonUtils.getJsonObject(ballotBoxJson);
            }

            @mockit.Mock
            JsonObject readElectoralAuthorityJsonObject(Path electoralAuthorityPath) throws IOException {
                return JsonUtils.getJsonObject(electoralAuthorityJson);

            }

            @mockit.Mock
            void writeEncryptionParamsInputIntoTxtFile(Path ballotBoxPath, String electionEventId)
                    throws IOException, GeneralCryptoLibException {
            }

            @mockit.Mock
            Path getBallotBoxPath(JsonObject ballotBox, String ballotBoxId, String electionEventId) {
                return pathMock;
            }

            @mockit.Mock
            private String createMixingConfigJson(Path ballotBoxPath, String encryptedBallotsPathsString,
                    String ballotBoxId, String electionEventId, String signingKeyPEM, String ballotDecryptionKey)
                    throws ApplicationException {

                return applicationConfigurationJson;
            }

            @mockit.Mock
            private List<Path> getEncryptedBallotsPaths(Path ballotBoxPath, String tenantId, String electionEventId,
                    String ballotBoxId, int voteSetIdIndex) throws ResourceNotFoundException {
                return Collections.emptyList();
            }

            @mockit.Mock
            List<Path> doMixDecVal(String mixingConfigJsonStr, String ballotBoxId, String electionId) {
                return isValid ? Arrays.asList(Paths.get("/tmp")) : null;
            }

            @mockit.Mock
            void signMixingProofs(String signingKeyPEM, String electionEventId, String ballotBoxId,
                    List<Path> mixingDirectories) {

            }

            @mockit.Mock
            void signMixingProofsWithMetadata(PrivateKey signingKey, LinkedHashMap<String, String> fields,
                    Path... paths) {

            }

            @mockit.Mock
            LinkedHashMap<String, String> getMetadataFields(String electionEventId, String ballotBoxId) {
                return new LinkedHashMap<>();
            }

            @mockit.Mock
            PrivateKey getSigningKeyFromPEM(String privateKeyPEM) {
                return privateKeyMock;
            }

        };
    }
}
