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

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
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.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import java.util.Set;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.stores.bean.KeyStoreType;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.products.ov.config.io.ServicesInput;
import com.scytl.products.ov.config.utils.ChainValidator;
import com.scytl.products.ov.config.utils.KeyPairValidator;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.KeyStoreLoader;
import com.scytl.products.ov.utils.PasswordReaderUtils;

import mockit.Mock;
import mockit.MockUp;

public class GenerateLoggingKeysCommandTest {

    public static final String OUTPUT_PATH = "target/loggingKeys/";

    public static final char[] ROOT_PASSWORD = "1111111111111111".toCharArray();

    public static final String PRIVATE_KEY_ALIAS = "privatekey";

    public static final String SIGN_ALIAS = "signerkey";

    public static final String ENCRYPT_ALIAS = "encryptionkey";

    public static final String LOG_ENCRYPTION_SUFFIX = "_log_encryption";

    public static final String LOG_SIGNING_SUFFIX = "_log_signing";

    private static final String ROOT_KS_PATH = "src/test/resources/testMaterial/platformRoot/swisspostRootCA.sks";

    private static final String SERVICES_JSON = "src/test/resources/serviceCertificateParameters.json";

    private static Map<String, String> params2values;

    private static KeyPairValidator keyPairValidator;

    private GenerateLoggingKeysCommand _sut;

    @BeforeClass
    public static void setUp() {

        Security.addProvider(new BouncyCastleProvider());

        String[] inputParams = {"-out", OUTPUT_PATH, "-ks", ROOT_KS_PATH, "-ser", SERVICES_JSON };
        params2values = Action.GEN_LOGGING_KEYS.validateAndArrange(inputParams);

        keyPairValidator = new KeyPairValidator();

    }

    @Test
    public void generateLoggingKeysForTheSpecifiedServices()
            throws IOException, GeneralCryptoLibException, CertificateException, NoSuchAlgorithmException,
            KeyStoreException, UnrecoverableKeyException {

        _sut = new GenerateLoggingKeysCommand(new PrimitivesService());

        new MockUp<PasswordReaderUtils>() {

            @Mock
            public char[] readPasswordFromConsole() {
                return "1111111111111111".toCharArray();
            }
        };

        Map<String, String> service2passwords;

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos);) {
            PrintStream old = System.out;
            System.setOut(ps);

            _sut.execute(params2values);
            ps.flush();
            System.setOut(old);

            System.out.println(baos.toString());
            service2passwords = getPasswords(baos);
        }

        ServicesInput servicesInput = getServicesNameFromJson(Paths.get(SERVICES_JSON));
        JsonServiceNamesProvider serviceNamesProvider = new JsonServiceNamesProvider();
        Set<String> serviceNames = serviceNamesProvider.getServiceNames(servicesInput);

        CryptoAPIScytlKeyStore rootKeyStore =
            KeyStoreLoader.loadPlatformRootKeyStore(Paths.get(ROOT_KS_PATH), ROOT_PASSWORD);
        Certificate platformRootCA = rootKeyStore.getCertificateChain(PRIVATE_KEY_ALIAS)[0];

        for (String service : serviceNames) {

            String logEncPassword = service2passwords.get(service + LOG_ENCRYPTION_SUFFIX);

            validateLogEncryptionKeys(service, logEncPassword.toCharArray(), platformRootCA);

            String logSignPassword = service2passwords.get(service + LOG_SIGNING_SUFFIX);

            validateLogSigningKeys(service, logSignPassword.toCharArray(), platformRootCA);

        }

    }

    private Map<String, String> getPasswords(final ByteArrayOutputStream baos) {

        Map<String, String> map = new HashMap<>();

        String[] lines = baos.toString().split(System.lineSeparator());

        for (int i = 0; i < lines.length; i++) {
            String[] splitLine = lines[i].split(Constants.EQUAL);
            if (splitLine.length == 2) {
                map.put(splitLine[0], splitLine[1]);
            }
        }

        return map;
    }

    private void validateLogSigningKeys(final String service, final char[] logSignPassword,
            final Certificate platformRootCA)
            throws IOException, GeneralCryptoLibException, CertificateException, KeyStoreException,
            NoSuchAlgorithmException, UnrecoverableKeyException {

        final Path pathCertificate = Paths.get(OUTPUT_PATH, service + LOG_SIGNING_SUFFIX + Constants.PEM);
        Certificate logSignCert =
            PemUtils.certificateFromPem(new String(Files.readAllBytes(pathCertificate), StandardCharsets.UTF_8));

        CryptoX509Certificate cryptoCert = new CryptoX509Certificate((X509Certificate) logSignCert);

        assertThat(cryptoCert.getSubjectDn().getCommonName().contains(service), is(true));

        ChainValidator.validateChain(platformRootCA, logSignCert, X509CertificateType.SIGN);
        PublicKey publicKey = logSignCert.getPublicKey();

        final Path pathKeyStore = Paths.get(OUTPUT_PATH, service + LOG_SIGNING_SUFFIX + ".p12");

        byte[] keyStoreBytes = Files.readAllBytes(pathKeyStore);

        KeyStore keyStore = KeyStore.getInstance(KeyStoreType.PKCS12.getKeyStoreTypeName());
        keyStore.load(new ByteArrayInputStream(keyStoreBytes), logSignPassword);

        Certificate[] certificates = keyStore.getCertificateChain(SIGN_ALIAS);
        ChainValidator.validateChain(certificates[1], certificates[0], X509CertificateType.SIGN);

        PrivateKey privateKey = (PrivateKey) keyStore.getKey(SIGN_ALIAS, logSignPassword);

        keyPairValidator.validateKeyPair(publicKey, privateKey);
    }

    private void validateLogEncryptionKeys(final String service, final char[] password,
            final Certificate platformRootCA)
            throws IOException, GeneralCryptoLibException, CertificateException, KeyStoreException,
            NoSuchAlgorithmException, UnrecoverableKeyException {

        final Path pathCertificate = Paths.get(OUTPUT_PATH, service + LOG_ENCRYPTION_SUFFIX + Constants.PEM);
        Certificate logEncCert =
            PemUtils.certificateFromPem(new String(Files.readAllBytes(pathCertificate), StandardCharsets.UTF_8));

        CryptoX509Certificate cryptoCert = new CryptoX509Certificate((X509Certificate) logEncCert);

        assertThat(cryptoCert.getSubjectDn().getCommonName().contains(service), is(true));

        ChainValidator.validateChain(platformRootCA, logEncCert, X509CertificateType.ENCRYPT);
        PublicKey publicKey = logEncCert.getPublicKey();

        final Path pathKeyStore = Paths.get(OUTPUT_PATH, service + LOG_ENCRYPTION_SUFFIX + ".p12");

        byte[] keyStoreBytes = Files.readAllBytes(pathKeyStore);

        KeyStore keyStore = KeyStore.getInstance(KeyStoreType.PKCS12.getKeyStoreTypeName());
        keyStore.load(new ByteArrayInputStream(keyStoreBytes), password);

        Certificate[] certificates = keyStore.getCertificateChain(ENCRYPT_ALIAS);
        ChainValidator.validateChain(certificates[1], certificates[0], X509CertificateType.ENCRYPT);

        PrivateKey privateKey = (PrivateKey) keyStore.getKey(ENCRYPT_ALIAS, password);

        keyPairValidator.validateKeyPair(publicKey, privateKey);
    }

    private ServicesInput getServicesNameFromJson(final Path pathToJson) {

        ConfigObjectMapper _configObjectMapper = new ConfigObjectMapper();
        ServicesInput servicesInput = null;
        try {
            servicesInput = _configObjectMapper.fromJSONFileToJava(pathToJson.toFile(), ServicesInput.class);
        } catch (IOException e) {
            throw new RuntimeException("An error occurred while loading the services file", e);
        }

        return servicesInput;
    }
}
