/*
 * 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.config.webapp.mvc.commands.voters;

import static java.nio.file.Files.notExists;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.List;

import org.springframework.web.bind.annotation.RequestBody;

import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.config.commands.voters.VotersParametersHolder;
import com.scytl.products.ov.config.webapp.mvc.dto.input.CreateVotingCardSetInput;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.utils.ConfigObjectMapper;

public class VotersWebappAdapter {

    private final ConfigObjectMapper mapper;

    public VotersWebappAdapter(final ConfigObjectMapper mapper) {
        this.mapper = mapper;
    }

    public VotersParametersHolder adapt(@RequestBody final CreateVotingCardSetInput input) {

        final int numberVotingCards;
        final String ballotID;
        final String ballotBoxID;
        final String votingCardSetID;
        final String verificationCardSetID;
        final String electoralAuthorityID;
        final Path absoluteBasePath;
        final int numCredentialsPerFile;
        final int numProcessors;
        final String eeID;
        final Ballot ballot;
        List<String> choiceCodesEncryptionKeyAsListStrings;
        VotersParametersHolder holder;

        numberVotingCards = input.getNumberVotingCards();
        ballotID = input.getBallotID();
        ballotBoxID = input.getBallotBoxID();
        votingCardSetID = input.getVotingCardSetID();
        verificationCardSetID = input.getVerificationCardSetID();
        electoralAuthorityID = input.getElectoralAuthorityID();
        choiceCodesEncryptionKeyAsListStrings = input.getChoiceCodesEncryptionKey();
        String primeEncryptionPrivateKeyJson = input.getPrimeEncryptionPrivateKeyJson();

        checkGivenIDsAreUUIDs(ballotID, ballotBoxID, votingCardSetID);

        absoluteBasePath = parseBaseToAbsolutePath(input.getBasePath());
        eeID = absoluteBasePath.getFileName().toString();

        ballot = getBallot(input.getBallotPath());

        validateBallotAndBallotIDMatch(ballot, ballotID);

        numCredentialsPerFile = input.getNumCredentialsPerFile();

        numProcessors = getNumProcessors(input.getNumProcessors());

        final ZonedDateTime startValidityPeriod;
        final ZonedDateTime endValidityPeriod;

        final String start = input.getStart();
        final String end = input.getEnd();

        final Integer validityPeriod = input.getValidityPeriod();

        startValidityPeriod = ZonedDateTime.now(ZoneOffset.UTC);

        final ZonedDateTime electionStartDate = ZonedDateTime.ofInstant(Instant.parse(start), ZoneOffset.UTC);
        final ZonedDateTime electionEndDate = ZonedDateTime.ofInstant(Instant.parse(end), ZoneOffset.UTC);

        final String platformRootCACertificate = input.getPlatformRootCACertificate();

        endValidityPeriod = electionEndDate.plusYears(validityPeriod);

        if (electionEndDate.isAfter(endValidityPeriod)) {
            throw new IllegalArgumentException("End date cannot be after Start date plus validity period.");
        }

        String keyForProtectingKeystorePassword = input.getKeyForProtectingKeystorePassword();

        holder = new VotersParametersHolder(numberVotingCards, ballotID, ballot, ballotBoxID, votingCardSetID,
            verificationCardSetID, electoralAuthorityID, absoluteBasePath, numCredentialsPerFile, numProcessors, eeID,
            startValidityPeriod, endValidityPeriod, keyForProtectingKeystorePassword, input.getVotingCardSetAlias(),
            choiceCodesEncryptionKeyAsListStrings, primeEncryptionPrivateKeyJson, platformRootCACertificate,
            input.getCreateVotingCardSetCertificateProperties());

        return holder;
    }

    private int getNumProcessors(final int numProcessorsInput) {
        final int numProcessors;

        if (numProcessorsInput != 0) {
            numProcessors = numProcessorsInput;
            if (numProcessors < 1) {
                throw new IllegalArgumentException("The minimum number of processors should be 1.");
            }
            if (numProcessors > Runtime.getRuntime().availableProcessors()) {
                throw new IllegalArgumentException(
                    "The given number of processors is higher than the maximum allowed, which is: "
                        + Runtime.getRuntime().availableProcessors() + ". ");
            }
        } else {
            numProcessors = 1;
        }
        return numProcessors;
    }

    private Path parseBaseToAbsolutePath(final String basePath) {

        final Path baseAbsolutePath = Paths.get(basePath).toAbsolutePath();

        final String prefixErrorMessage = "The given base path: \"" + basePath;

        checkFile(basePath, baseAbsolutePath);

        if (!containsEEID(baseAbsolutePath)) {
            throw new IllegalArgumentException(prefixErrorMessage
                + "\" requires an election event id in UUID format (e.g. 17ccbe962cf341bc93208c26e911090c). ");
        }

        return baseAbsolutePath;
    }

    private void checkGivenIDsAreUUIDs(final String ballotID, final String ballotBoxID, final String votingCardSetID) {
        if (!isUUID(ballotID, ballotBoxID, votingCardSetID)) {
            throw new IllegalArgumentException("The given UUIDs must comply with the required UUID format (e.g., "
                + "17ccbe962cf341bc93208c26e911090c)");
        }
    }

    private boolean containsEEID(final Path baseAbsolutePath) {

        final String eeid = baseAbsolutePath.getFileName().toString();

        return isUUID(eeid);
    }

    private boolean isUUID(final String... ids) {

        boolean matches = true;

        for (final String id : ids) {
            matches &= id.matches(Constants.HEX_ALPHABET);
        }
        return matches;
    }

    private Ballot getBallot(final String ballotPath) {

        final Path ballotAbsolutePath = Paths.get(ballotPath).toAbsolutePath();

        final File ballotFile = ballotAbsolutePath.toFile();

        checkFile(ballotPath, ballotAbsolutePath);

        return getBallotFromFile(ballotPath, ballotFile);
    }

    private Ballot getBallotFromFile(final String ballotPath, final File ballotFile) {
        Ballot ballot;
        try {
            ballot = mapper.fromJSONFileToJava(ballotFile, Ballot.class);
        } catch (final IOException e) {
            throw new IllegalArgumentException(
                "An error occurred while mapping \"" + ballotPath + "\" to a Ballot.", e);
        }
        return ballot;
    }

    private void validateBallotAndBallotIDMatch(final Ballot ballot, final String ballotID) {
        if (!ballot.getId().equals(ballotID)) {
            throw new IllegalArgumentException("The given Ballot with ID: " + ballot.getId()
                + " and the given ballotID: " + ballotID + " are different. They must be the same.");
        }
    }

    private void checkFile(final String path, final Path absolutePath) {
        final String errorMessageBallot = "The given file: \"" + path + "\"";

        final String errorHelpMessage =
            " The given path should be either (1) relative to the execution path or (2) absolute.";

        if (notExists(absolutePath)) {
            throw new IllegalArgumentException(errorMessageBallot + " could not be found." + errorHelpMessage);
        }
    }
}
