/*
 * 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.config.it.verifiers;

import static java.util.Arrays.fill;

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.PasswordProtection;
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.ArrayList;
import java.util.List;
import java.util.Map;

import javax.security.auth.DestroyFailedException;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.ov.commons.beans.BallotBox;
import com.scytl.products.ov.commons.beans.BallotBoxContextData;
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.readers.ConfigurationInputReader;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.PasswordEncrypter;

/**
 * A class to execute all validations related with Create Ballot Boxes command
 */
public class CreateBallotBoxesVerifier {
    private static final Logger LOG = LoggerFactory.getLogger("std");

    private final ConfigObjectMapper _mapper = new ConfigObjectMapper();

    private final KeyPairValidator _keyPairValidator = new KeyPairValidator();

    private final ScytlKeyStoreService _scytlKeyStoreService;

    private final Map<String, CryptoX509Certificate> _loadedCertificates;

    private final ConfigurationInputReader _configInputReader = new ConfigurationInputReader();

    public CreateBallotBoxesVerifier(final Map<String, CryptoX509Certificate> loadedCertificates)
            throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());
        _scytlKeyStoreService = new ScytlKeyStoreService();
        _loadedCertificates = loadedCertificates;
    }

    /**
     * @param eeidFolder
     * @param bid
     * @param bbid
     * @param privateKeyToDecryptKeystorePassword
     * @throws IOException
     * @throws GeneralCryptoLibException
     * @throws CertificateException
     */
    public void createBallotBoxesValidations(final File eeidFolder, final String bid, final String bbid,
            final PrivateKey privateKeyToDecryptKeystorePassword)
            throws IOException, GeneralCryptoLibException, CertificateException {

        List<String> bbIdsList = new ArrayList<>();
        bbIdsList.add(bbid);

        onlineFolderValidations(eeidFolder, bid, bbIdsList, privateKeyToDecryptKeystorePassword);
    }

    private void onlineFolderValidations(final File eeidFolder, final String bid, final List<String> bbIdsList,
            final PrivateKey privateKeyToDecryptKeystorePassword)
            throws IOException, CertificateException, GeneralCryptoLibException {

        Path ballotsBoxesFolder = Paths.get(eeidFolder.getAbsolutePath(), Constants.ONLINE_DIRECTORY,
            Constants.ELECTION_INFORMATION_DIRECTORY, Constants.BALLOTS_DIRECTORY, bid,
            Constants.BALLOT_BOXES_DIRECTORY);
        Assert.assertTrue(Files.exists(ballotsBoxesFolder));

        for (String bbid : bbIdsList) {

            ReplacementsHolder replacementsHolder = new ReplacementsHolder(eeidFolder.getName(), bbid);
            CertificateValidator certificateValidator = new CertificateValidator(replacementsHolder);

            Path ballotBoxIdFolder = Paths.get(ballotsBoxesFolder.toString(), bbid);
            Assert.assertTrue(Files.exists(ballotBoxIdFolder));

            Path ballotBoxJSONPath = Paths.get(ballotBoxIdFolder.toString(), Constants.BALLOT_BOX_FILENAME);
            Assert.assertTrue(Files.exists(ballotBoxJSONPath));

            BallotBox ballotBox = _mapper.fromJSONFileToJava(ballotBoxJSONPath.toFile(), BallotBox.class);
            Assert.assertEquals(bid, ballotBox.getBid());
            Assert.assertEquals(bbid, ballotBox.getId());

            // cert chain validation
            // 3) electionEventCA -> servicesCA -> ballotBox
            CryptoX509Certificate ballotBoxCert =
                KeyLoader.convertPEMStringtoCryptoX509Certificate(ballotBox.getBallotBoxCert());

            certificateValidator.validateCert(ballotBoxCert, getBallotBoxCredentialProperties());

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

            Path ballotBoxContentDataJSONPath =
                Paths.get(ballotBoxIdFolder.toString(), Constants.BALLOTBOX_CONTEXTDATA_FILENAME);
            Assert.assertTrue(Files.exists(ballotBoxContentDataJSONPath));

            BallotBoxContextData ballotBoxContextData =
                _mapper.fromJSONFileToJava(ballotBoxContentDataJSONPath.toFile(), BallotBoxContextData.class);
            Assert.assertEquals(bbid, ballotBoxContextData.getId());

            byte[] decodedKeystore =
                Base64.decodeBase64(ballotBoxContextData.getKeystore().getBytes(StandardCharsets.UTF_8));
            InputStream in = new ByteArrayInputStream(decodedKeystore);

            char[] keystorePassword =
                getKeystorePassword(ballotBoxContextData.getPasswordKeystore(), privateKeyToDecryptKeystorePassword);
            PasswordProtection passwordProtection = new PasswordProtection(keystorePassword);

            CryptoAPIScytlKeyStore keystore;
            try {
                keystore = _scytlKeyStoreService.loadKeyStoreFromJSON(in, passwordProtection);
            } finally {
                fill(keystorePassword, ' ');
                try {
                    passwordProtection.destroy();
                } catch (DestroyFailedException e) {
                    LOG.warn("Failed to destroy password", e);
                }
            }

            Assert.assertTrue(keystore.getPrivateKeyAliases().size() == 1);
            String alias = keystore.getPrivateKeyAliases().get(0);
            Certificate[] certChain = keystore.getCertificateChain(alias);

            CryptoX509Certificate[] cryptoX509Certificates =
                KeyLoader.fromCertificateArrayToCryptoCertificateArray(certChain);

            // the order of certs to be passed to checkChain method is: root,
            // intermediate, leaf
            Assert.assertTrue(certificateValidator
                .checkChain(cryptoX509Certificates[2], cryptoX509Certificates[1], cryptoX509Certificates[0])
                .size() == 0);

            PrivateKey privateKey =
                keystore.getPrivateKeyEntry(alias, ballotBoxContextData.getPasswordKeystore().toCharArray());
            PublicKey publicKey = certChain[0].getPublicKey();
            _keyPairValidator.validateKeyPair(publicKey, privateKey);
        }
    }

    private char[] getKeystorePassword(final String passwordKeystore, final PrivateKey privateKey)
            throws GeneralCryptoLibException, IOException {

        AsymmetricService asymmetricService = new AsymmetricService();

        PasswordEncrypter passwordEncrypter = new PasswordEncrypter(asymmetricService);

        return passwordEncrypter.decryptPassword(passwordKeystore, privateKey);
    }

    private CredentialProperties getBallotBoxCredentialProperties() throws IOException {
        Path keysConfigJSON = Paths.get("./src/test/resources/keys_config.json");
        ConfigurationInput configurationInput = _configInputReader.fromFileToJava(keysConfigJSON.toFile());
        return configurationInput.getBallotBox();
    }
}
