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

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.StringJoiner;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.products.oscore.logging.api.writer.LoggingWriter;
import com.scytl.products.ov.commons.beans.VerificationCardSetData;
import com.scytl.products.ov.commons.beans.VoteVerificationContextData;
import com.scytl.products.ov.config.commands.voters.JobExecutionObjectContext;
import com.scytl.products.ov.config.commands.voters.VotersParametersHolder;
import com.scytl.products.ov.config.commands.voters.VotersSerializationDestProvider;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardSetCredentialDataPack;
import com.scytl.products.ov.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.config.logs.Loggable;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.KeyStoreReader;

class WriteVerificationDataTasklet implements Tasklet {

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

    private VotersSerializationDestProvider destProvider;

    private JobExecutionObjectContext objectContext;

    @Loggable
    private LoggingWriter logWriter;

    public WriteVerificationDataTasklet(final VotersSerializationDestProvider destProvider,
            final JobExecutionObjectContext objectContext) {
        this.destProvider = destProvider;
        this.objectContext = objectContext;
    }

    @Override
    public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) throws Exception {

        final VotingCardGenerationJobExecutionContext jobExecutionContext = new VotingCardGenerationJobExecutionContext(
            chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext());

        try {

            final String jobInstanceId = jobExecutionContext.getJobInstanceId();
            final String electionEventId = jobExecutionContext.getElectionEventId();
            final String verificationCardSetId = jobExecutionContext.getVerificationCardSetId();
            final String electoralAuthorityId = jobExecutionContext.getElectoralAuthorityId();

            final VotersParametersHolder votersParametersHolder =
                objectContext.get(jobInstanceId, VotersParametersHolder.class);

            final VerificationCardSetCredentialDataPack verificationCardSetCredentialDataPack =
                objectContext.get(jobInstanceId, VerificationCardSetCredentialDataPack.class);

            final VotingCardSetCredentialDataPack votingCardSetCredentialDataPack =
                objectContext.get(jobInstanceId, VotingCardSetCredentialDataPack.class);

            final VerificationCardSetData verificationCardSetData = new VerificationCardSetData();
            verificationCardSetData.setVerificationCardSetId(verificationCardSetId);
            verificationCardSetData.setElectionEventId(electionEventId);

            final ElGamalPublicKey choiceCodesPublicKey =
                verificationCardSetCredentialDataPack.getChoiceCodesEncryptionPublicKey();
            String choiceCodesPublicKeyB64;
            try {
                choiceCodesPublicKeyB64 =
                    new String(Base64.encodeBase64(choiceCodesPublicKey.toJson().getBytes(StandardCharsets.UTF_8)),
                        StandardCharsets.UTF_8);
            } catch (GeneralCryptoLibException e) {
                throw new CreateVotingCardSetException(
                    "An error occurred while attempting to serialize the choices code publickey: " + e.getMessage(), e);
            }
            verificationCardSetData.setChoicesCodesEncryptionPublicKey(choiceCodesPublicKeyB64);

            final String verificationCardIssuerCert =
                getPemEncodedCertificate(verificationCardSetCredentialDataPack.getVerificationCardIssuerCert());
            verificationCardSetData.setVerificationCardIssuerCert(verificationCardIssuerCert);

            final String voteCastCodeSignerCert =
                getPemEncodedCertificate(votingCardSetCredentialDataPack.getVoteCastCodeSignerCertificate());
            verificationCardSetData.setVoteCastCodeSignerCert(voteCastCodeSignerCert);

            final ConfigObjectMapper configObjectMapper = new ConfigObjectMapper();

            final Path verDataSetDataJSON = destProvider.getVerificationCardSetData();
            final File verificationCardSetDataFile = verDataSetDataJSON.toFile();
            configObjectMapper.fromJavaToJSONFile(verificationCardSetData, verificationCardSetDataFile);

            final VoteVerificationContextData voteVerificationContextData = new VoteVerificationContextData();
            voteVerificationContextData.setElectionEventId(electionEventId);
            voteVerificationContextData.setVerificationCardSetId(verificationCardSetId);
            voteVerificationContextData.setEncryptionParameters(votersParametersHolder.getEncryptionParameters());

            voteVerificationContextData.setElectoralAuthorityId(electoralAuthorityId);

            final String serializedCodesSecretKeyKeyStoreB64 = KeyStoreReader
                .toString(votingCardSetCredentialDataPack.getKeyStore(), votingCardSetCredentialDataPack.getPassword());
            voteVerificationContextData.setCodesSecretKeyKeyStore(serializedCodesSecretKeyKeyStoreB64);
            voteVerificationContextData
                .setCodesSecretKeyPassword(votingCardSetCredentialDataPack.getEncryptedPassword());

            final ElGamalPublicKey[] nonCombinedChoiceCodesEncryptionPublicKeys =
                verificationCardSetCredentialDataPack.getNonCombinedChoiceCodesEncryptionPublicKeys();
            StringJoiner nonCombinedChoiceCodesPublicKeysB64Builder = new StringJoiner(";");
            for (ElGamalPublicKey nonCombinedElGamalPublicKey : nonCombinedChoiceCodesEncryptionPublicKeys) {

                try {
                    String nonCombinedChoiceCodesPublicKeyB64 = new String(
                        Base64.encodeBase64(nonCombinedElGamalPublicKey.toJson().getBytes(StandardCharsets.UTF_8)),
                        StandardCharsets.UTF_8);
                    nonCombinedChoiceCodesPublicKeysB64Builder.add(nonCombinedChoiceCodesPublicKeyB64);
                } catch (GeneralCryptoLibException e) {
                    throw new CreateVotingCardSetException(
                        "An error occurred while attempting to serialize one of the non combined choices code publickeys: "
                            + e.getMessage(),
                        e);
                }
            }
            voteVerificationContextData
                .setNonCombinedChoiceCodesEncryptionPublicKeys(nonCombinedChoiceCodesPublicKeysB64Builder.toString());

            final Path voteVerContextDataJSON = destProvider.getVoteVerificationContextData();
            final File voteVerificationContextDataFile = voteVerContextDataJSON.toFile();
            configObjectMapper.fromJavaToJSONFile(voteVerificationContextData, voteVerificationContextDataFile);

        } catch (Exception e) {
            LOGGER.error("Write verification data task failed.", e);
            throw e;
        }
        return RepeatStatus.FINISHED;
    }

    private String getPemEncodedCertificate(final CryptoAPIX509Certificate certificate) {
        final byte[] certPEMArray = certificate.getPemEncoded();
        return new String(certPEMArray, StandardCharsets.UTF_8);
    }
}
