/*
 * 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;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.PublicKey;

import com.scytl.cryptolib.certificates.utils.LdapHelper;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

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.commons.verify.MetadataFileVerifier;
import com.scytl.products.ov.tallying.api.dto.output.TallyingOutput;
import com.scytl.products.ov.tallying.command.TallyingHolder;
import com.scytl.products.ov.tallying.commons.beans.ErrorCodes;
import com.scytl.products.ov.tallying.commons.beans.TallyingError;
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;

/**
 * Class helps to verify signature of result files produced during cleansing and decrypting process.
 */
public class SignatureVerifier {

    private static final String DECRYPTED_VOTES_ERROR_MSG =
        "The decrypted ballot box signature verification failed, tallying aborted.";

    private static final String AUDITABLE_VOTES_ERROR_MSG =
        "The auditable votes file signature verification failed, tallying aborted.";

    private static final String FAILED_VOTES_ERROR_MSG =
        "The failed votes file signature verification failed, tallying aborted.";

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

    @Autowired
    private SecureLoggingWriter secureLog;

    @Autowired
    private HashService hashService;

    private final MetadataFileVerifier metadataFileVerifier;

    public SignatureVerifier(MetadataFileVerifier metadataFileVerifier) {
        this.metadataFileVerifier = metadataFileVerifier;
    }

    public boolean verifyDecryptedVotesFileSignature(final TallyingHolder holder, final TallyingOutput output)
            throws TallyingException {

        Path csvFilePath = holder.getDecryptedBallotsPath();
        LOG.info("Starting to verify decrypted votes file " + csvFilePath.getFileName() + " signature...");

        return verify(holder, csvFilePath, output, TallyingLogEvents.DECRYPTED_VOTES_FILE_SIGNATURE_VALID,
            TallyingLogEvents.DECRYPTED_VOTES_FILE_SIGNATURE_INVALID, DECRYPTED_VOTES_ERROR_MSG);
    }

    public boolean verifyAuditableVotesFileSignature(final TallyingHolder holder, final TallyingOutput output)
            throws TallyingException {

        Path csvFilePath = holder.getAuditableVotesPath();
        LOG.info("Starting to verify auditable votes file " + csvFilePath.getFileName() + " signature...");

        return verify(holder, csvFilePath, output, TallyingLogEvents.AUDITABLE_VOTES_FILE_SIGNATURE_VALID,
            TallyingLogEvents.AUDITABLE_VOTES_FILE_SIGNATURE_INVALID, AUDITABLE_VOTES_ERROR_MSG);
    }

    public boolean verifyFailedVotesFileSignature(TallyingHolder holder, TallyingOutput output)
            throws TallyingException {

        Path csvFilePath = holder.getFailedVotesPath();
        LOG.info("Starting to verify failed votes file " + csvFilePath.getFileName() + " signature...");

        return verify(holder, csvFilePath, output, TallyingLogEvents.FAILED_VOTES_FILE_SIGNATURE_VALID,
            TallyingLogEvents.FAILED_VOTES_FILE_SIGNATURE_INVALID, FAILED_VOTES_ERROR_MSG);
    }

    private boolean verify(TallyingHolder holder, Path csvFilePath, TallyingOutput output,
            TallyingLogEvents infoSecureLogEvent, TallyingLogEvents errorSecureLogEvent, String errorMessage)
            throws TallyingException {

        final Path metadataFilePath = csvFilePath.resolveSibling(csvFilePath.getFileName() + ".metadata");

        PublicKey publicKey = holder.getSignatureCert().getPublicKey();

        try (FileChannel ch = FileChannel.open(csvFilePath, StandardOpenOption.READ);
                InputStream metadataFileIn = new FileInputStream(metadataFilePath.toFile());
        		InputStream csvFileInHash = Channels.newInputStream(ch.position(0));) {

            String fileHash = hashService.getHash(csvFileInHash, csvFilePath.toAbsolutePath().toString());

            try (InputStream csvFileInVerifySignature = Channels.newInputStream(ch.position(0))) {

	            boolean success = metadataFileVerifier.verifySignature(publicKey, metadataFileIn, csvFileInVerifySignature);
	            if (success) {
	
	                String certCN = new LdapHelper().getAttributeFromDistinguishedName(holder.getSignatureCert()
	                        .getSubjectDN().toString(), "CN");
	
	                secureLog.log(Level.INFO,
	                        new LogContent.LogContentBuilder().logEvent(infoSecureLogEvent).objectId(holder.getBallotBoxId())
	                                .user("adminID").electionEvent(holder.getElectionEventId())
	                                .additionalInfo(TallyingLogConstants.FILE_NAME, String.valueOf(csvFilePath.getFileName()))
	                                .additionalInfo(TallyingLogConstants.FILE_HASH, fileHash)
	                                .additionalInfo(TallyingLogConstants.CERT_COMMON_NAME, certCN)
	                                .additionalInfo(TallyingLogConstants.CERT_SERIAL_NUMBER,
	                                        String.valueOf(holder.getSignatureCert().getSerialNumber()))
	                                .createLogInfo());
	            } else {
	                logSignatureVerificationError(output, holder, csvFilePath, fileHash, errorMessage, errorSecureLogEvent);
	            }
	
	            return success;
            }
        } catch (IOException | GeneralCryptoLibException e) {
            logSignatureVerificationError(output, holder, csvFilePath, "",
                ExceptionUtils.getRootCauseMessage(e), errorSecureLogEvent);
            throw new TallyingException(errorMessage, e);
        }
    }

    private void logSignatureVerificationError(TallyingOutput output, TallyingHolder holder, Path csvFilePath,
            String fileHash, String errorMsg, TallyingLogEvents errorSecureLogEvent) {

        LdapHelper ldapHelper = new LdapHelper();
        String certCN = ldapHelper.getAttributeFromDistinguishedName(holder.getSignatureCert().getSubjectDN().toString(), "CN");

        secureLog.log(Level.ERROR, new LogContent.LogContentBuilder().logEvent(errorSecureLogEvent)
            .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
            .additionalInfo(TallyingLogConstants.FILE_NAME, String.valueOf(csvFilePath.getFileName()))
            .additionalInfo(TallyingLogConstants.FILE_HASH, fileHash)
            .additionalInfo(TallyingLogConstants.CERT_COMMON_NAME, certCN)
            .additionalInfo(TallyingLogConstants.CERT_SERIAL_NUMBER,
                String.valueOf(holder.getSignatureCert().getSerialNumber()))
            .additionalInfo(TallyingLogConstants.ERR_DESC, errorMsg).createLogInfo());

        LOG.error(errorMsg);
        output.addError(new TallyingError(DECRYPTED_VOTES_ERROR_MSG, ErrorCodes.SIGNATURE_VALIDATION_EXCEPTION));
    }
}
