/*
 * 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.tallying.webapp.mvc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Security;
import java.util.List;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.verify.MetadataFileVerifier;
import com.scytl.products.ov.tallying.command.TallyingHolder;
import com.scytl.products.ov.tallying.webapp.mvc.dto.input.TallyingWebappInput;
import com.scytl.products.ov.tallying.webapp.spring.ResourcePathResolverConfig;
import com.scytl.products.ov.tallying.webapp.spring.WebAppTestConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebAppTestConfig.class, ResourcePathResolverConfig.class })
@WebAppConfiguration
public class TallyingEngineControllerITest {

    private static final String FILES_PATH =
        "f58a52111f2c41e18b5e6d25b07076f9/ONLINE/electionInformation/ballots/81d8fb95d446496997939fdcb064f95a/ballotBoxes/1234/";

    private MockMvc mockMvc;

    @Autowired
    private TallyingEngineController controller;

    @BeforeClass
    public static void setUp() {
        Security.addProvider(new BouncyCastleProvider());
    }

    @AfterClass
    public static void removeBouncyCastleProvider() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Before
    public void beforeMethod() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void testServiceCheck() throws Exception {
        this.mockMvc.perform(get("/check")).andExpect(status().isOk());
    }

    @Test
    public void perform_tallying_correctly() throws Exception {

        KeyPair keyPair = GeneratingUtils.generateAndStoreCertificateAndReturnKeyPair("certId",
            new File(this.getClass().getResource("/csr/certId.pem").toURI()).toPath());

        TallyingWebappInput input = new TallyingWebappInput();
        input.setBallot(generateBallot());
        input.setBallotBoxID("1234");
        input.setWriteInSeparator("#");
        input.setTenantID("100");
        input.setCertId("certId");
        input.setPrivateKeyPEM(PemUtils.privateKeyToPem(keyPair.getPrivate()));

        TallyingHolder holder = new TallyingHolder();
        holder.setSignatureKey(keyPair.getPrivate());
        holder.setTenantId(input.getTenantID());
        holder.setElectionEventId("f58a52111f2c41e18b5e6d25b07076f9");
        holder.setBallotBoxId(input.getBallotBoxID());
        holder.setWriteInSeparator("#");

        GeneratingUtils
            .sign(new File(this.getClass().getClassLoader().getResource(FILES_PATH + "decryptedBallots.csv").toURI())
                .toPath(), holder);
        GeneratingUtils.sign(
            new File(this.getClass().getClassLoader().getResource(FILES_PATH + "auditableVotes.csv").toURI()).toPath(),
            holder);
        GeneratingUtils.sign(
            new File(this.getClass().getClassLoader().getResource(FILES_PATH + "failedVotes.csv").toURI()).toPath(),
            holder);

        RequestBuilder request = post("/tally").content(GeneratingUtils.asJsonString(input))
            .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);

        this.mockMvc.perform(request).andExpect(status().isOk());

        MvcResult result = this.mockMvc.perform(request).andReturn();

        JsonNode errors = new ObjectMapper().readTree(result.getResponse().getContentAsByteArray()).get("errors");

        assertEquals(0, errors.size());

        checkGeneratedFiles(keyPair.getPublic());

    }

    private void checkGeneratedFiles(PublicKey publicKey)
            throws URISyntaxException, IOException, GeneralCryptoLibException {

        File invalidVotesFile =
            new File(this.getClass().getClassLoader().getResource(FILES_PATH + "tally/invalidVotes.csv").toURI());
        List<String> invalidVotes = Files.readAllLines(invalidVotesFile.toPath(), StandardCharsets.UTF_8);
        assertEquals(3, invalidVotes.size());
        assertEquals("1;cleansing_invalid_votes", invalidVotes.get(0));
        assertEquals("3;decryption_invalid_votes", invalidVotes.get(1));
        assertEquals("4;total_invalid_votes", invalidVotes.get(2));

        checkSignature(invalidVotesFile, publicKey);

        File talliedVotesFile = new File(this.getClass().getClassLoader()
            .getResource(FILES_PATH + "tally/27aa7af87de444758e58623b88431d6b/27aa7af87de444758e58623b88431d6b.csv")
            .toURI());
        List<String> talliedVotes = Files.readAllLines(talliedVotesFile.toPath(), StandardCharsets.UTF_8);
        assertEquals(3, talliedVotes.size());
        assertEquals("2;17;23;29;37", talliedVotes.get(0));
        assertEquals("2;29;37;53;59", talliedVotes.get(1));
        assertEquals("1;11;13;17;23", talliedVotes.get(2));

        checkSignature(talliedVotesFile, publicKey);

        talliedVotesFile = new File(this.getClass().getClassLoader()
            .getResource(FILES_PATH + "tally/d1d24859cbef408b816a1467b0b3afc7/d1d24859cbef408b816a1467b0b3afc7.csv")
            .toURI());
        talliedVotes = Files.readAllLines(talliedVotesFile.toPath(), StandardCharsets.UTF_8);
        assertEquals(3, talliedVotes.size());
        assertEquals("3;2", talliedVotes.get(0));
        assertEquals("1;3", talliedVotes.get(1));
        assertEquals("1;5", talliedVotes.get(2));

        checkSignature(talliedVotesFile, publicKey);
    }

    private void checkSignature(File file, PublicKey publicKey) throws IOException, GeneralCryptoLibException {
        final Path metadataFilePath = file.toPath().resolveSibling(file.getName() + ".metadata");

        try (FileInputStream csvFileIn = new FileInputStream(file);
                FileInputStream metadataFileIn = new FileInputStream(metadataFilePath.toFile())) {

            boolean success =
                new MetadataFileVerifier(new AsymmetricService()).verifySignature(publicKey, metadataFileIn, csvFileIn);
            assertTrue(success);
        }
    }

    private Ballot generateBallot() {
        return GeneratingUtils.generateBallot("f58a52111f2c41e18b5e6d25b07076f9", "81d8fb95d446496997939fdcb064f95a",
            "d1d24859cbef408b816a1467b0b3afc7", "alias_1", "votation", "27aa7af87de444758e58623b88431d6b", "alias_2s",
            "election");
    }

}
