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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
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.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetIdImpl;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.ConfigObjectMapper;
import com.scytl.products.ov.mixnet.commons.configuration.ApplicationConfig;
import com.scytl.products.ov.mixnet.commons.configuration.file.FileBasedMixerConfig;
import com.scytl.products.ov.mixnet.commons.configuration.locations.LocationConstants;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfig.class })
@WebAppConfiguration
public class MixDecValControllerTest {

    @Autowired
    private MixDecValController controller;

    private MockMvc mockMvc;

    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

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

    /**
     * Ensure the ballot can be correctly deserialised from an actual JSON
     * sample.
     */
    @Test
    public void testBallotDeserialization() throws IOException {
        String filename = "src/test/resources/mixdecval/ballot.json";

        ObjectMapper objectMapper = new ObjectMapper();
        Ballot ballot = objectMapper.readValue(new File(filename), Ballot.class);

        assertNotNull(ballot);
    }

    @Test
    public void checkTest() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/check").accept(MediaType.TEXT_PLAIN))
            .andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string(""))
            .andDo(print());
    }

    @Test
    public void executeMixingReturnsHttpStatus200WhenNoErrors() throws Exception {
        // Copy mixing config folder to temporary location, for trouble-free
        // manipulation.
        File sourceConfigFolder = new File("src/test/resources/mixdecval/config");
        Path ballotBoxFolder = tempFolder.getRoot().toPath();
        FileUtils.copyDirectory(sourceConfigFolder, tempFolder.getRoot());

        // Load the sample JSON request body.
        String requestBodyFileName = "src/test/resources/mixdecval/request_body.json";
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode mixdecvalRequestBody = objectMapper.readTree(new File(requestBodyFileName));

        // Update the configuration paths to match the newly created temp
        // folder.
        ((ObjectNode) mixdecvalRequestBody.path("mixer")).put("outputDirectory", ballotBoxFolder.toString());
        JsonNode mixerInputNode = mixdecvalRequestBody.path("mixer").path("input");
        ((ObjectNode) mixerInputNode).put("publicKey", ballotBoxFolder.resolve("publicKey").toString());

        String voteSetId = new VoteSetIdImpl(
            new BallotBoxIdImpl("100", "31de77e77b0f4a55b67604a6d0797e13", "cecb3ac6a1854b7590e5aeaf7f74d801"), 0)
                .toString();

        Path ccn_m1Path = ballotBoxFolder.resolve(voteSetId + "-ccn_m1.json");
        Path ccn_m2Path = ballotBoxFolder.resolve(voteSetId + "-ccn_m2.json");
        Path ccn_m3Path = ballotBoxFolder.resolve(voteSetId + "-ccn_m3.json");

        List<Path> encryptedBallotsPaths = Arrays.asList(ccn_m1Path, ccn_m2Path, ccn_m3Path);
        String encryptedBallotsPathsString = ObjectMappers.toJson(encryptedBallotsPaths);

        ((ObjectNode) mixerInputNode).put("encryptedBallots", encryptedBallotsPathsString);
        ((ObjectNode) mixerInputNode).put("encryptionParameters",
            ballotBoxFolder.resolve("encryptedParams").toString());

        JsonNode verifierNode = mixdecvalRequestBody.path("verifier");
        ((ObjectNode) verifierNode).put("inputDirectory", ballotBoxFolder.toString());
        ((ObjectNode) verifierNode).put("outputDirectory", ballotBoxFolder.toString());
        JsonNode validatorNode = mixdecvalRequestBody.path("validator");
        ((ObjectNode) validatorNode).put("outputPath", ballotBoxFolder.toString());

        // Run the test with the updated configuration
        String jsonResponse = mockMvc
            .perform(MockMvcRequestBuilders.post("/execute").contentType(MediaType.APPLICATION_JSON)
                .content(mixdecvalRequestBody.toString()))
            .andDo(print()).andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse()
            .getContentAsString();

        MixDecValResult result = objectMapper.readValue(jsonResponse, MixDecValResult.class);

        assertNull(result.getError());
        assertEquals(1, result.getMixingDirectories().size());
        assertTrue(result.getMixingDirectories().get(0).toFile().exists());
        assertTrue(result.getMixingDirectories().get(0).resolve("votesWithProof.csv").toFile().exists());
        assertTrue(result.getMixingDirectories().get(0).resolve("votes.csv").toFile().exists());
        assertTrue(result.getMixingDirectories().get(0).resolve("proofs.json").toFile().exists());
        assertTrue(result.getMixingDirectories().get(0).resolve("encryptedBallots.csv").toFile().exists());
        assertTrue(result.getMixingDirectories().get(0).resolve("reencryptedBallots.csv").toFile().exists());

        assertTrue(ballotBoxFolder.resolve("decompressedVotes.csv").toFile().exists());
        assertTrue(ballotBoxFolder.resolve("auditableVotes.csv").toFile().exists());
    }

    @Test
    public void executeMixingReturnsHttpStatus4xxWhenEmptyConfiguration() throws Exception {

        ApplicationConfig invalidConfig = new ApplicationConfig();

        mockMvc
            .perform(MockMvcRequestBuilders.post("/execute").contentType("application/json")
                .content(new ConfigObjectMapper().fromJavaToJSON(invalidConfig)))
            .andExpect(MockMvcResultMatchers.status().is4xxClientError()).andDo(print());

    }

    @SuppressWarnings("serial")
    @Test
    public void executeMixingReturnsHttpStatus4xxWhenMissingConcurrencyParams() throws Exception {

        MixDecValConfig invalidConfig = new MixDecValConfig();
        FileBasedMixerConfig mixerConfig = new FileBasedMixerConfig();
        mixerConfig.setMix(true);
        mixerConfig.setInput(new HashMap<String, String>() {
            {
                put(LocationConstants.PUBLIC_KEY_JSON_TAG, "src/test/resources/input/public_key");
                put(LocationConstants.ENCRYPTED_BALLOTS_JSON_TAG, "src/test/resources/input/encrypted_ballots");
                put(LocationConstants.ENCRYPTION_PARAMETERS_JSON_TAG,
                    "src/test/resources/input/parameters/encryption_parameters");
            }
        });
        invalidConfig.setMixer(mixerConfig);

        mockMvc
            .perform(MockMvcRequestBuilders.post("/execute").contentType("application/json")
                .content(new ConfigObjectMapper().fromJavaToJSON(invalidConfig)))
            .andExpect(MockMvcResultMatchers.status().is4xxClientError()).andDo(print());

    }

    @Test
    public void executeMixingReturnsHttpStatus4xxWhenMissingMixerConfiguration() throws Exception {

        ApplicationConfig invalidConfig = new ApplicationConfig();
        invalidConfig.setConcurrencyLevel(1);
        invalidConfig.setMinimumBatchSize(1);
        invalidConfig.setNumberOfParallelBatches(1);
        invalidConfig.setNumOptions(10);

        mockMvc
            .perform(MockMvcRequestBuilders.post("/execute").contentType("application/json")
                .content(new ConfigObjectMapper().fromJavaToJSON(invalidConfig)))
            .andExpect(MockMvcResultMatchers.status().is4xxClientError()).andDo(print());

    }
}
