/*
 * 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.commons.io.writer;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.tallying.command.TallyingHolder;
import com.scytl.products.ov.tallying.commons.HashService;
import com.scytl.products.ov.tallying.commons.beans.TalliedInvalidVotes;
import com.scytl.products.ov.tallying.commons.exception.TallyingException;
import com.scytl.products.ov.tallying.commons.log.TallyingLogConstants;
import com.scytl.products.ov.tallying.commons.log.TallyingLogEvents;

/**
 * A class that writes the results of the tallying of invalid (auditable and failed) votes to a csv file.
 */
public class TalliedInvalidVotesWriter {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    private final HashService hashService;

    private static final String VALUE_SEPARATOR = ";";

    private static final String AUDITABLE_VOTES_LABEL = "decryption_invalid_votes";

    private static final String FAILED_VOTES_LABEL = "cleansing_invalid_votes";

    private static final String TOTAL_VOTES_LABEL = "total_invalid_votes";

    private final SignResultWriter signResultWriter;

    private final SecureLoggingWriter loggingWriter;

    public TalliedInvalidVotesWriter(SignResultWriter signResultWriter, HashService hashService,
            SecureLoggingWriter loggingWriter) {
        this.signResultWriter = signResultWriter;
        this.hashService = hashService;
        this.loggingWriter = loggingWriter;
    }

    public void writeToFiles(final TalliedInvalidVotes talliedInvalidVotes, final TallyingHolder holder)
            throws TallyingException {

        LOG.info("Persisting tallied invalid votes in directory " + holder.getOutputDir());

        if (!Files.exists(holder.getOutputDir())) {
            try {
                Files.createDirectory(holder.getOutputDir());
            } catch (IOException e) {
                logErrorDuringTallyingFileCreating(holder, e);
                throw new TallyingException(TallyingLogEvents.TALLYING_FILE_CREATION_FAILED.getInfo(), e);
            }
        }

        List<String> resultValues = prepareResult(talliedInvalidVotes);

        String tallyFileAbsolutePath;
        try {
            tallyFileAbsolutePath = writeToFile(holder.getOutputDir(), resultValues, holder.getInvalidVotesFileName());
            String fileHash = hashService.getHash(tallyFileAbsolutePath);
            loggingWriter.log(Level.INFO,
                new LogContent.LogContentBuilder().logEvent(TallyingLogEvents.TALLYING_FILE_CREATED_SUCCESSFULLY)
                    .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_ID, holder.getInvalidVotesFileName())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_HASH, fileHash).createLogInfo());

        } catch (IOException e) {
            logErrorDuringTallyingFileCreating(holder, e);
            throw new TallyingException("Error during incorrect tallying file creating", e);
        }

        String fileHash = hashService.getHash(tallyFileAbsolutePath);

        try {
            signResultWriter.signOutput(Paths.get(tallyFileAbsolutePath), holder);
            loggingWriter.log(Level.INFO,
                new LogContent.LogContentBuilder().logEvent(TallyingLogEvents.TALLYING_FILE_SIGNED_SUCCESSFULLY)
                    .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_ID, holder.getInvalidVotesFileName())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_HASH, fileHash).createLogInfo());
        } catch (IOException | GeneralCryptoLibException e) {
            loggingWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(TallyingLogEvents.TALLYING_FILE_SIGNING_FAILED)
                    .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_ID, holder.getInvalidVotesFileName())
                    .additionalInfo(TallyingLogConstants.TALLY_SHEET_HASH, fileHash)
                    .additionalInfo(TallyingLogConstants.ERR_DESC, ExceptionUtils.getRootCauseMessage(e))
                    .createLogInfo());
            String errorMessage = "Error occurred during signing tallying file " + tallyFileAbsolutePath;
            LOG.error(errorMessage, e);
            throw new TallyingException(errorMessage, e);
        }

        LOG.info("Tallying results saved in " + tallyFileAbsolutePath);
    }

    private void logErrorDuringTallyingFileCreating(TallyingHolder holder, IOException e) {
        loggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder().logEvent(TallyingLogEvents.TALLYING_FILE_CREATION_FAILED)
                .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
                .additionalInfo(TallyingLogConstants.TALLY_SHEET_ID, holder.getInvalidVotesFileName())
                .additionalInfo(TallyingLogConstants.ERR_DESC, ExceptionUtils.getRootCauseMessage(e)).createLogInfo());
    }

    private String writeToFile(Path outputFileDir, List<String> resultValues, String invalidVotesFilename)
            throws IOException {

        File contextOutputDir = outputFileDir.toFile();

        if (!contextOutputDir.exists() && !contextOutputDir.mkdir()) {
            String msg = "Directory creating failed: " + contextOutputDir;
            LOG.error(msg);
            throw new IOException(msg);
        }

        String tallyFileAbsolutePath = contextOutputDir.getAbsolutePath() + File.separator + invalidVotesFilename;
        LOG.info("Persisting tallied invalid votes in file " + tallyFileAbsolutePath);

		try (FileWriter fileWriter = new FileWriter(tallyFileAbsolutePath);
				BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
				PrintWriter writer = new PrintWriter(bufferedWriter)) {

            resultValues.forEach(writer::println);
        }

        LOG.info("Result was written to " + tallyFileAbsolutePath);

        return tallyFileAbsolutePath;
    }

    private List<String> prepareResult(TalliedInvalidVotes contestVotes) {

        List<String> resultValues = new ArrayList<>();
        LOG.info("Preparing tallied invalid votes output...");

        String failedVotes = contestVotes.getNumberOfFailedVotes() + VALUE_SEPARATOR + FAILED_VOTES_LABEL;
        String auditableVotes = contestVotes.getNumberOfAuditableVotes() + VALUE_SEPARATOR + AUDITABLE_VOTES_LABEL;
        String totalVotes = contestVotes.getTotalNumberOfVotes() + VALUE_SEPARATOR + TOTAL_VOTES_LABEL;

        resultValues.add(failedVotes);
        resultValues.add(auditableVotes);
        resultValues.add(totalVotes);

        LOG.info("Tallied invalid votes are ready.");

        return resultValues;
    }
}
