/*
 * 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.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;

import javax.json.Json;
import javax.servlet.http.HttpServletRequest;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.sign.MetadataFileSigner;
import com.scytl.products.ov.commons.sign.SignatureMetadata;
import com.scytl.products.ov.commons.sign.beans.SignatureFieldsConstants;
import com.scytl.products.ov.tallying.path.PathResolver;
import com.scytl.products.ov.tallying.webapp.mvc.dto.input.TallyingWebappInput;
import com.scytl.products.ov.tallying.webapp.mvc.dto.output.TallyingWebappOutput;
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 TallyingEngineControllerTest {

    @Autowired
    private TallyingEngineController _tallyingEngineController;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private MetadataFileSigner metadataFileSigner;

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

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

    @Test
    public void execute_the_tallying_correctly() throws Exception {

        TallyingWebappInput input = generateInput();

        final ResponseEntity<TallyingWebappOutput> tally =
            _tallyingEngineController.tally(input, Mockito.mock(HttpServletRequest.class));

        assertThat(tally.getBody().getErrors().size(), is(0));
        assertThat(tally.getStatusCode(), is(HttpStatus.valueOf(200)));

    }

    @Test
    public void check_tallying() {

        final ResponseEntity<Void> check = _tallyingEngineController.serviceCheck();

        assertThat(check.getStatusCode(), is(HttpStatus.OK));
    }

    private TallyingWebappInput generateInput() throws GeneralCryptoLibException, IOException, URISyntaxException {

        Path certificatePath = pathResolver.resolve("").resolve("csr").resolve("certId.pem");
        File file = certificatePath.toFile();
        file.getParentFile().mkdirs();
        KeyPair keyPair = GeneratingUtils.generateAndStoreCertificateAndReturnKeyPair("certId", certificatePath);
        final TallyingWebappInput input = new TallyingWebappInput();

        String electionEventId = "f58a52111f2c41e18b5e6d25b07076f9";
        String ballotBoxId = "81d8fb95d446496997939fdcb064f95a";
        final Ballot ballot =
            GeneratingUtils.generateBallot(electionEventId, ballotBoxId, "d1d24859cbef408b816a1467b0b3afc7", "alias_1",
                "votation", "27aa7af87de444758e58623b88431d6b", "alias_2s", "election");
        Path ballots = pathResolver.resolve(electionEventId).resolve("ONLINE/electionInformation/ballots")
            .resolve(ballotBoxId).resolve("ballotBoxes/1234/decryptedBallots.csv");
        Path failedVotes = pathResolver.resolve(electionEventId).resolve("ONLINE/electionInformation/ballots")
            .resolve(ballotBoxId).resolve("ballotBoxes/1234/failedVotes.csv");
        Path auditableVotes = pathResolver.resolve(electionEventId).resolve("ONLINE/electionInformation/ballots")
            .resolve(ballotBoxId).resolve("ballotBoxes/1234/auditableVotes.csv");
        signFile(ballots, electionEventId, ballotBoxId, keyPair.getPrivate());
        signFile(failedVotes, electionEventId, ballotBoxId, keyPair.getPrivate());
        signFile(auditableVotes, electionEventId, ballotBoxId, keyPair.getPrivate());
        input.setBallot(ballot);
        input.setBallotBoxID("1234");
        input.setWriteInSeparator("#");
        input.setTenantID("100");
        input.setPrivateKeyPEM(PemUtils.privateKeyToPem(keyPair.getPrivate()));
        input.setCertId("certId");

        return input;
    }

    private void signFile(final Path originalFilePath, String electionEvent, String ballotBoxId, PrivateKey key)
            throws IOException, GeneralCryptoLibException {
        LinkedHashMap<String, String> fields = extractFields(electionEvent, ballotBoxId);

        final SignatureMetadata signature =
            metadataFileSigner.createSignature(key, Files.newInputStream(originalFilePath), fields);

        final Path metadataFilePath =
            Paths.get(originalFilePath.getParent().toString(), originalFilePath.getFileName() + ".metadata");

        try (OutputStream os = Files.newOutputStream(metadataFilePath, StandardOpenOption.CREATE)) {
            Json.createWriter(os).writeObject(signature.toJsonObject());
        }

    }

    private LinkedHashMap<String, String> extractFields(String electionEvent, String ballotBoxId) {

        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put(SignatureFieldsConstants.SIG_FIELD_TENANTID, "100");
        fields.put(SignatureFieldsConstants.SIG_FIELD_EEVENTID, electionEvent);
        fields.put(SignatureFieldsConstants.SIG_FIELD_BBOXID, ballotBoxId);
        fields.put(SignatureFieldsConstants.SIG_FIELD_TIMESTAMP, DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
        fields.put(SignatureFieldsConstants.SIG_FIELD_COMPONENT, "tallying");

        return fields;
    }
}
