/*
 * 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.sdm.config.spring.batch.writers;

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

import org.apache.commons.codec.binary.Base64;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.transform.LineAggregator;
import org.springframework.core.io.FileSystemResource;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
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.LoggingWriter;
import com.scytl.products.ov.commons.beans.VerificationCardPublicKeyAndSignature;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardCredentialDataPack;
import com.scytl.products.ov.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.config.exceptions.specific.GenerateVerificationCardDataException;
import com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents;
import com.scytl.products.ov.sdm.config.spring.batch.GeneratedVotingCardOutput;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.FingerprintGenerator;

public class VerificationCardDataWriter extends FlatFileItemWriter<GeneratedVotingCardOutput> {

    private static final ConfigObjectMapper mapper = new ConfigObjectMapper();

    private final FingerprintGenerator fingerprintGenerator;

    private final LoggingWriter loggingWriter;

    public VerificationCardDataWriter(final Path path, final FingerprintGenerator fingerprintGenerator,
            final LoggingWriter loggingWriter) {
        this.fingerprintGenerator = fingerprintGenerator;
        this.loggingWriter = loggingWriter;

        setLineAggregator(lineAggregator());
        setTransactional(false);
        setAppendAllowed(false);
        setShouldDeleteIfExists(true);
        setResource(new FileSystemResource(path.toString()));
    }

    private LineAggregator<GeneratedVotingCardOutput> lineAggregator() {

        return item -> {

            final String verificationCardId = item.getVerificationCardId();
            final String verificationCardSetId = item.getVerificationCardSetId();
            final String electionEventId = item.getElectionEventId();
            final VerificationCardCredentialDataPack verificationCardCredentialDataPack =
                item.getVerificationCardCredentialDataPack();

            final String verificationCardSerializedKeyStoreB64 =
                verificationCardCredentialDataPack.getSerializedKeyStore();

            confirmAndLogKeystore(verificationCardSerializedKeyStoreB64, verificationCardId, verificationCardSetId,
                electionEventId, loggingWriter);

            final ElGamalPublicKey verificationCardPublicKey =
                verificationCardCredentialDataPack.getVerificationCardKeyPair().getPublicKeys();
            String verificationCardPublicKeyB64;
            try {
                verificationCardPublicKeyB64 =
                    new String(Base64.encodeBase64(verificationCardPublicKey.toJson().getBytes(StandardCharsets.UTF_8)),
                        StandardCharsets.UTF_8);
            } catch (GeneralCryptoLibException e) {
                throw new CreateVotingCardSetException(
                    "Exception while trying to obtain a representation of the verification card publickey: "
                        + e.getMessage(),
                    e);
            }

            final byte[] signatureBytes = verificationCardCredentialDataPack.getSignatureVCardPubKeyEEIDVCID();
            final String signatureBase64 = new String(Base64.encodeBase64(signatureBytes), StandardCharsets.UTF_8);
            VerificationCardPublicKeyAndSignature verCardBean = new VerificationCardPublicKeyAndSignature();
            verCardBean.setPublicKey(verificationCardPublicKeyB64);
            verCardBean.setSignature(signatureBase64);

            String serializedVerCardBeanB64 = null;
            try {
                serializedVerCardBeanB64 =
                    Base64.encodeBase64String(mapper.fromJavaToJSON(verCardBean).getBytes(StandardCharsets.UTF_8));
            } catch (JsonProcessingException e) {
                throw new CreateVotingCardSetException("Exception while trying to encode verification card to Json", e);
            }

            return String.format("%s,%s,%s,%s,%s", verificationCardId, verificationCardSerializedKeyStoreB64,
                serializedVerCardBeanB64, electionEventId, verificationCardSetId);
        };
    }

    private void confirmAndLogKeystore(final String verificationCardSerializedKeyStoreB64,
            final String verificationCardId, final String verificationCardSetId, final String electionEventId,
            final LoggingWriter loggingWriter) {

        if (verificationCardSerializedKeyStoreB64.length() > 0) {
            final String ksCid = fingerprintGenerator.generate(verificationCardSerializedKeyStoreB64);

            loggingWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_VERIFICATION_CARD_KEYSTORE)
                    .electionEvent(electionEventId).user("adminID").additionalInfo("verifcs_id", verificationCardSetId)
                    .additionalInfo("verifc_id", verificationCardId).additionalInfo("ks_cid", ksCid).createLogInfo());

        } else {
            final String errorMsg = "Error - the keyStore that is being written to file is invalid";
            loggingWriter.log(Level.ERROR, new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_VERIFICATION_KEYSTORE)
                .electionEvent(electionEventId).user("adminID").additionalInfo("verifcs_id", verificationCardSetId)
                .additionalInfo("verifc_id", verificationCardId).additionalInfo("err_desc", errorMsg).createLogInfo());

            throw new GenerateVerificationCardDataException(errorMsg);
        }
    }
}
