/*
 * 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 java.io.BufferedWriter;
import java.io.FileWriter;
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.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.beans.Contest;
import com.scytl.products.ov.commons.beans.ElectionAttributes;
import com.scytl.products.ov.commons.beans.ElectionEvent;
import com.scytl.products.ov.commons.beans.ElectionOption;
import com.scytl.products.ov.commons.beans.Question;
import com.scytl.products.ov.commons.sign.MetadataFileSigner;
import com.scytl.products.ov.tallying.command.TallyingHolder;
import com.scytl.products.ov.tallying.commons.io.writer.SignResultWriter;

public class GeneratingUtils {

    public static KeyPair generateAndStoreCertificateAndReturnKeyPair(String commonName, Path certificatePath)
            throws GeneralCryptoLibException, IOException, URISyntaxException {

        KeyPair keyPair = new AsymmetricService().getKeyPairForSigning();
        ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
        ZonedDateTime end = now.plusYears(1);
        ValidityDates validityDates = new ValidityDates(Date.from(now.toInstant()), Date.from(end.toInstant()));

        CertificateData certificateData = new CertificateData();
        certificateData.setSubjectPublicKey(keyPair.getPublic());
        certificateData.setValidityDates(validityDates);
        X509DistinguishedName distinguishedName = new X509DistinguishedName.Builder(commonName, "ES").build();
        certificateData.setSubjectDn(distinguishedName);
        certificateData.setIssuerDn(distinguishedName);

        CryptoAPIX509Certificate cert =
            new CertificatesService().createSignX509Certificate(certificateData, keyPair.getPrivate());

        Files.deleteIfExists(certificatePath);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(certificatePath.toFile()))) {
            writer.write(new String(cert.getPemEncoded(), StandardCharsets.UTF_8));
            writer.close();
        }

        return keyPair;
    }

    public static void sign(final Path filePath, final TallyingHolder holder)
            throws IOException, GeneralCryptoLibException {
        SignResultWriter signResultWriter = new SignResultWriter(new MetadataFileSigner(new AsymmetricService()));
        signResultWriter.signOutput(filePath, holder);
    }

    public static Ballot generateBallot(String electionEventId, String ballotBoxId, String contestId1,
            String contestAlias1, String contestType1, String contestId2, String contestAlias2, String contestType2) {
        ElectionEvent electionEvent = new ElectionEvent(electionEventId);

        List<Contest> contests = new ArrayList<>();
        String attribute = "";

        List<ElectionOption> options1 = new ArrayList<>();
        options1.add(new ElectionOption("68c8ef17c29848eca3cd0beeea3a7a23", "2", attribute));
        options1.add(new ElectionOption("0416a036b43e4b999d338a930afd5e31", "3", attribute));
        options1.add(new ElectionOption("87396e39be244b72a31c29d90a1fb1d9", "5", attribute));

        List<ElectionOption> options2 = new ArrayList<>();
        options2.add(new ElectionOption("b2dbe2cdeb524037b18e9e05bbaac8a0", "11", attribute));
        options2.add(new ElectionOption("4c44edd7f54e412aaebe335a8fccd61f", "13", attribute));
        options2.add(new ElectionOption("976ff671f50343e0b872f5731485c091", "17", attribute));
        options2.add(new ElectionOption("169e9d4ed4934f7bbc289cb02f90401e", "23", attribute));
        options2.add(new ElectionOption("c71e07507dc84e39977fefc5e570ca4e", "29", attribute));
        options2.add(new ElectionOption("c57e3fdf407542f6bfcf28c804955f34", "37", attribute));
        options2.add(new ElectionOption("886414e104664bf88fe695e455db70e2", "53", attribute));
        options2.add(new ElectionOption("8f80f11845d94b6bae2b2d1ef21a14f8", "59", attribute));
        options2.add(new ElectionOption("b274c7c5f65e446eb86ec93e8fb51c3d", "67", attribute));
        options2.add(new ElectionOption("d7dc813e18a44f8cbf6d7c15eff5ed1c", "73", attribute));

        Contest contest1 = new Contest(contestId1, contestType1, contestType1, contestAlias1, contestType1, "false",
            options1, emptyEAList(), emptyQuestionList(), "encryptedCorrectnessRule", "decryptedCorrectnessRule");
        Contest contest2 = new Contest(contestId2, contestType2, contestType2, contestAlias2, contestType2, "false",
            options2, emptyEAList(), emptyQuestionList(), "encryptedCorrectnessRule", "decryptedCorrectnessRule");
        contests.add(contest1);
        contests.add(contest2);

        return new Ballot(ballotBoxId, electionEvent, contests);
    }

    private static List<Question> emptyQuestionList() {
        return new ArrayList<>();
    }

    private static List<ElectionAttributes> emptyEAList() {
        return new ArrayList<>();
    }

    public static Ballot generateBallot(BallotInformation bi) {
        ElectionEvent electionEvent = new ElectionEvent(bi.getElectionId());

        List<Contest> contests = new ArrayList<>();
        String attribute = "";
        String[] primes = {"2", "3", "5", "11", "13", "17", "23", "29", "37", "53", "59", "67", "73", "79" };
        List<ElectionOption> options1 = new ArrayList<>();
        options1.add(new ElectionOption(generateId(), primes[0], attribute));
        options1.add(new ElectionOption(generateId(), primes[1], attribute));
        options1.add(new ElectionOption(generateId(), primes[2], attribute));
        options1.add(new ElectionOption(generateId(), primes[3], attribute));

        List<ElectionOption> options2 = new ArrayList<>();
        for (int i = options1.size(); i < (primes.length / 3) * 2; i++) {
            options2.add(new ElectionOption(generateId(), primes[i], attribute));
        }

        List<ElectionOption> options3 = new ArrayList<>();
        for (int i = (primes.length / 3) * 2; i < primes.length; i++) {
            options3.add(new ElectionOption(generateId(), primes[i], attribute));
        }

        List<ElectionAttributes> optionAttributes = new ArrayList<>();
        List<List<ElectionOption>> contestOptions = new ArrayList<>();
        contestOptions.add(options1);
        contestOptions.add(options2);
        contestOptions.add(options3);
        for (int i = 0; i < bi.getContests().size(); i++) {
            ContestInformation ci = bi.getContests().get(i);
            contests.add(new Contest(ci.getContestName(), ci.getContestName(), ci.getContestName(), ci.getContestName(),
                ci.getContestType(), "false", contestOptions.get(i), optionAttributes, emptyQuestionList(),
                "encryptedCorrectnessRule", "decryptedCorrectnessRule"));

        }

        return new Ballot(bi.getBbId(), electionEvent, contests);
    }

    private static String generateId() {
        return UUID.randomUUID().toString();
    }

    public static String asJsonString(final Object obj) throws JsonProcessingException {
        final ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(obj);
    }
}
