/*
 * 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
 */
/**
 * Created by jruiz on 2/06/15.
 */
package com.scytl.products.ov.config.it.verifiers;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.ov.commons.beans.VerificationCardSetData;
import com.scytl.products.ov.config.it.utils.CertificateValidator;
import com.scytl.products.ov.config.it.utils.KeyLoader;
import com.scytl.products.ov.config.it.utils.KeyPairValidator;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.datapacks.beans.ConfigurationInput;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;
import com.scytl.products.ov.datapacks.helpers.ReplacementsHolder;
import com.scytl.products.ov.utils.ConfigObjectMapper;

public class CreateVotingCardSetsVerifier {

    private final KeyPairValidator _keyPairValidator = new KeyPairValidator();

    private final ScytlKeyStoreServiceAPI _storesService;

    private final Map<String, CryptoX509Certificate> _loadedCertificates;

    private final ConfigObjectMapper _configObjectMapper = new ConfigObjectMapper();

    private final ConfigurationInput _configurationInput;

    private String _eeid;

    public CreateVotingCardSetsVerifier(final Map<String, CryptoX509Certificate> loadedCertificates) {

        Security.addProvider(new BouncyCastleProvider());

        _loadedCertificates = loadedCertificates;

        Path keyConfigJSON = Paths.get("./src/test/resources/keys_config.json");
        try {
            _storesService = new ScytlKeyStoreService();
            _configurationInput = _configObjectMapper.fromJSONFileToJava(keyConfigJSON.toFile(),
                    ConfigurationInput.class);
        } catch (IOException | GeneralCryptoLibException e) {
            throw new RuntimeException("An error occurred when trying to load config properties.", e);
        }
    }

    public void createVerificationCardSetsValidations(final File eeidFolder, final int numChunks)
            throws IOException, CertificateException, GeneralCryptoLibException {
        _eeid = eeidFolder.getName();

        final Path onlineVoteVerificationFolder =
            Paths.get(eeidFolder.getAbsolutePath(), Constants.ONLINE_DIRECTORY, Constants.VOTE_VERIFICATION_DIRECTORY);
        final String verificationCardSetId = onlineVoteVerificationFolder.toFile().listFiles()[0].getName();
        final Path onlineVoteVerificationWithIdFolder =
            Paths.get(onlineVoteVerificationFolder.toString(), verificationCardSetId);
        validateVerificationCardSetData(onlineVoteVerificationWithIdFolder);
    }

    private void validateVerificationCardSetData(final Path onlineVoteVerificationFolder)
            throws IOException, GeneralCryptoLibException, CertificateException {

        final Path verificationCardSetDataPath =
            Paths.get(onlineVoteVerificationFolder.toString(), Constants.VERIFICATION_CARD_SET_DATA + Constants.JSON);
        VerificationCardSetData verificationCardSetData =
            _configObjectMapper.fromJSONFileToJava(verificationCardSetDataPath.toFile(), VerificationCardSetData.class);

        CertificateValidator certificateValidator = new CertificateValidator(new ReplacementsHolder(_eeid));

        Certificate verificationCardIssuerCert =
            PemUtils.certificateFromPem(verificationCardSetData.getVerificationCardIssuerCert());
        CryptoX509Certificate verificationCardIssuerX509Cert =
            KeyLoader.convertCertificateToCryptoCertificate(verificationCardIssuerCert);
        certificateValidator.validateCert(verificationCardIssuerX509Cert, _configurationInput.getVerificationCardSet());

        Certificate voteCastCodeSignerCert =
            PemUtils.certificateFromPem(verificationCardSetData.getVoteCastCodeSignerCert());
        CryptoX509Certificate voteCastCodeSignerX509Cert =
            KeyLoader.convertCertificateToCryptoCertificate(voteCastCodeSignerCert);
        certificateValidator.validateCert(voteCastCodeSignerX509Cert, _configurationInput.getVotingCardSet());
    }

    public void createVotingCardSetsValidations(final File eeidFolder, final String votingCardSetId,
            final int numChunks) throws IOException, GeneralCryptoLibException, CertificateException {
        _eeid = eeidFolder.getName();

        // adias: the following code is commented because it's using the
        // credential generation temporary OFFLINE
        // files to validate the generated ONLINE credential data. If we really
        // need to validate that the generated
        // certificates are correct then we should not rely on temporary data
        // for that.
        // I'm leaving the code here so that we eventually replace it by a more
        // 'correct' way of validation

        // final Path onlineFolder = Paths.get(eeidFolder.getAbsolutePath(),
        // Constants.ONLINE_DIRECTORY,
        // Constants.VOTER_MATERIAL_DIRECTORY, votingCardSetId);
        // final Path offlineVoterMaterialFolder =
        // Paths.get(eeidFolder.getAbsolutePath(), Constants.OFFLINE_DIRECTORY,
        // Constants.VOTER_MATERIAL_DIRECTORY, votingCardSetId);
        // validateCredentialDataChunks(onlineFolder,
        // offlineVoterMaterialFolder, numChunks);

    }

    private void validateCredentialDataChunks(final Path onlineOutputPath, final Path offlineOutputPath,
            final int numChunks) throws IOException, GeneralCryptoLibException, CertificateException {

        final Path vcids2PIN = Paths.get(offlineOutputPath.toString(), Constants.VCID_TO_PIN_FILENAME + Constants.CSV);
        final List<String> pins = Files.readAllLines(vcids2PIN);
        int indexPin = 0;
        for (int chunk = 1; chunk <= numChunks; chunk++) {
            final Path credentialData = Paths.get(onlineOutputPath.toString(),
                Constants.CREDENTIAL_DATA_FILENAME + "_" + chunk + Constants.CSV);

            final List<String> keyStores = Files.readAllLines(credentialData);

            for (int i = 0; i < keyStores.size(); i++) {

                final String pin = pins.get(indexPin).split(Constants.COMMA)[1];
                indexPin++;
                final String[] splitLine = keyStores.get(i).split(Constants.COMMA);
                final String credentialID = splitLine[0];
                final String keyStoreB64 = splitLine[1];

                final char[] pinChars = pin.toCharArray();
                final byte[] keyStore = Base64.decodeBase64(keyStoreB64.getBytes(StandardCharsets.UTF_8));

                try (InputStream in = new ByteArrayInputStream(keyStore)) {

                    final CryptoAPIScytlKeyStore cryptoAPIKeyStore =
                        _storesService.loadKeyStoreFromJSON(in, new KeyStore.PasswordProtection(pinChars));

                    for (final String alias : cryptoAPIKeyStore.getPrivateKeyAliases()) {

                        final PrivateKey privateKey = cryptoAPIKeyStore.getPrivateKeyEntry(alias, pinChars);
                        final Certificate[] certificateChain = cryptoAPIKeyStore.getCertificateChain(alias);

                        assertThat(certificateChain.length, is(3));
                        final PublicKey publicKey = certificateChain[0].getPublicKey();
                        _keyPairValidator.validateKeyPair(publicKey, privateKey);

                        // Certificate chain validation
                        // electionEventCA -> credentialsCA -> credentialsAuth
                        // electionEventCA -> credentialsCA -> credentialsSign

                        if (_loadedCertificates != null) {

                            CryptoX509Certificate cryptoX509Certificate =
                                KeyLoader.convertCertificateToCryptoCertificate(certificateChain[0]);

                            CertificateValidator certificateValidator =
                                new CertificateValidator(new ReplacementsHolder(_eeid, credentialID));
                            // validate certificate fields
                            certificateValidator.validateCert(cryptoX509Certificate,
                                getCredentialPropertiesByAlias(alias));

                            Assert
                                .assertTrue(
                                    certificateValidator
                                        .checkChain(_loadedCertificates.get("electioneventca"),
                                            _loadedCertificates.get("credentialsca"), cryptoX509Certificate)
                                        .size() == 0);

                        }

                    }
                }
            }
        }
    }

    private CredentialProperties getCredentialPropertiesByAlias(final String alias) throws IOException {
        switch (alias) {
        case "sign":
            return _configurationInput.getCredentialSign();
        case "auth_sign":
            return _configurationInput.getCredentialAuth();
        default:
            return null;
        }

    }

}
