/*
 * 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 static com.scytl.products.ov.constants.Constants.CREDENTIAL_ID;
import static com.scytl.products.ov.constants.Constants.KEYSTORE_PIN;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.apache.commons.configuration.ConfigurationException;
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 org.springframework.beans.factory.annotation.Autowired;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.products.ov.config.commands.voters.JobExecutionObjectContext;
import com.scytl.products.ov.config.commands.voters.VotersHolderInitializer;
import com.scytl.products.ov.config.commands.voters.VotersParametersHolder;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialInputDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardSetCredentialInputDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VerificationCardSetCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VotingCardSetCredentialDataPackGenerator;
import com.scytl.products.ov.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.constants.Constants;

class PrepareVotingCardGenerationDataTasklet implements Tasklet {

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

    @Autowired
    private JobExecutionObjectContext stepExecutionObjectContext;

    @Autowired
    private VotingCardSetCredentialDataPackGenerator votingCardSetCredentialDataPackGenerator;

    @Autowired
    private VerificationCardSetCredentialDataPackGenerator verificationCardSetCredentialDataPackGenerator;

    @Autowired
    private VotersHolderInitializer votersHolderInitializer;

    @Autowired
    private PrimitivesServiceAPI primitivesService;

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

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

            final String electionEventId = jobExecutionContext.getElectionEventId();
            final String jobInstanceId = jobExecutionContext.getJobInstanceId();
            final VotersParametersHolder parametersHolder =
                stepExecutionObjectContext.get(jobInstanceId, VotersParametersHolder.class);
            final String verificationCardSetId = jobExecutionContext.getVerificationCardSetId();            
            // setup all the data. the class is mutable which is not ideal..
            VotersParametersHolder updatedParametersHolder;
            try (InputStream is = getKeysConfiguration()) {
                updatedParametersHolder = votersHolderInitializer.init(parametersHolder, is);
            }
            stepExecutionObjectContext.put(jobInstanceId, updatedParametersHolder, VotersParametersHolder.class);

            String choiceCodesEncryptionKey = jobExecutionContext.getChoiceCodesEncryptionKey();

			final VerificationCardSetCredentialDataPack verificationCardSetCredentialDataPack = getVerificationCardSetCredentialDataPack(
					updatedParametersHolder, verificationCardSetId, choiceCodesEncryptionKey);
            stepExecutionObjectContext.put(jobInstanceId, verificationCardSetCredentialDataPack,
                VerificationCardSetCredentialDataPack.class);

            final VotingCardSetCredentialDataPack votingCardSetCredentialDataPack =
                getVotingCardSetCredentialDataPack(updatedParametersHolder);
            stepExecutionObjectContext.put(jobInstanceId, votingCardSetCredentialDataPack,
                VotingCardSetCredentialDataPack.class);

            byte[] saltCredentialId =
                primitivesService.getHash((CREDENTIAL_ID + electionEventId).getBytes(StandardCharsets.UTF_8));
            jobExecutionContext.setSaltCredentialId(Base64.getEncoder().encodeToString(saltCredentialId));
            byte[] saltPIN =
                primitivesService.getHash((KEYSTORE_PIN + electionEventId).getBytes(StandardCharsets.UTF_8));
            jobExecutionContext.setSaltPIN(Base64.getEncoder().encodeToString(saltPIN));

        } catch (CreateVotingCardSetException e) {
            LOGGER.error("Failed to generate card set data pack.", e);
            throw e;
        } catch (GeneralCryptoLibException e) {
            LOGGER.error("Failed salt (credential|pin) hash values.", e);
            throw e;
        } catch (Exception e) {
            LOGGER.error("Prepare voting card generation task failed.", e);
            throw e;
        }

        return RepeatStatus.FINISHED;
    }

    private InputStream getKeysConfiguration() {
        // this is not very pretty, but the file is in another module (config-engine) so we have to load the file
        // with the classloader (to search on whole classpath)
        return getClass().getClassLoader().getResourceAsStream(Constants.KEYS_CONFIG_FILENAME);
    }

	private VerificationCardSetCredentialDataPack getVerificationCardSetCredentialDataPack(
			final VotersParametersHolder holder, final String verificationCardSetID, String choiceCodesEncryptionKey) {

        final VerificationCardSetCredentialInputDataPack verificationCardSetCredentialInputDataPack =
            holder.getVerificationCardSetCredentialInputDataPack();

        final VerificationCardSetCredentialDataPack verificationCardSetCredentialDataPack;
        try {
            verificationCardSetCredentialDataPack = verificationCardSetCredentialDataPackGenerator.generate(
                verificationCardSetCredentialInputDataPack, verificationCardSetID, choiceCodesEncryptionKey,
                holder.getCreateVotingCardSetCertificateProperties().getVerificationCardSetCertificateProperties());
        } catch (ConfigurationException | IOException e) {
            throw new CreateVotingCardSetException(
                "An error occurred while generating the verification card set credential data pack: " + e.getMessage(),
                e);
        }
        return verificationCardSetCredentialDataPack;
    }

    private VotingCardSetCredentialDataPack getVotingCardSetCredentialDataPack(final VotersParametersHolder holder) {
        final VotingCardSetCredentialInputDataPack votingCardSetCredentialInputDataPack =
            holder.getVotingCardSetCredentialInputDataPack();

        final VotingCardSetCredentialDataPack votingCardSetCredentialDataPack;
        try {
            votingCardSetCredentialDataPack = votingCardSetCredentialDataPackGenerator.generate(
                votingCardSetCredentialInputDataPack, holder.getKeyForProtectingKeystorePassword(),
                holder.getCreateVotingCardSetCertificateProperties().getVotingCardSetCertificateProperties());
        } catch (ConfigurationException | IOException | GeneralCryptoLibException e) {
            throw new CreateVotingCardSetException(
                "An error occurred while generating the voting card set credential data pack: " + e.getMessage(), e);
        }
        return votingCardSetCredentialDataPack;
    }

}
