/*
 * 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 java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Map;

import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.products.ov.config.commons.exception.CertificateCommandException;
import com.scytl.products.ov.config.io.CredentialPropertiesProvider;
import com.scytl.products.ov.config.utils.FileUtils;
import com.scytl.products.ov.constants.CertificatePropertiesConstants;
import com.scytl.products.ov.csr.CSRLoader;
import com.scytl.products.ov.csr.CSRSigningInputProperties;
import com.scytl.products.ov.csr.CertificateRequestSigner;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;
import com.scytl.products.ov.utils.CertificatePersister;
import com.scytl.products.ov.utils.KeyStoreLoader;
import com.scytl.products.ov.utils.PasswordCleaner;
import com.scytl.products.ov.utils.PasswordReaderUtils;

public class CertificateCommand implements Command {

    public static final String SEPARATOR = "_";

    public static final int RANDOM_STRING_LENGTH = 26;

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

    private final PasswordCleaner passwordCleaner = new PasswordCleaner();

    @Override
    public void execute(final Map<String, String> params2values) {

        final Path platformRootKeyStorePath;
        try {
            platformRootKeyStorePath = Paths
                .get(Paths.get(params2values.get(ActionParams.KEYSTORE_PATH.getName())).toFile().getCanonicalPath());
        } catch (IOException e) {
            throw new CertificateCommandException("Could not get the absolute path of the keystore", e);
        }

        final Path tenantCSRPath;
        try {
            tenantCSRPath =
                Paths.get(Paths.get(params2values.get(ActionParams.CSR_PATH.getName())).toFile().getCanonicalPath());
        } catch (IOException e) {
            throw new CertificateCommandException("Could not get the absolute path of the CSR", e);
        }

        String fileName = tenantCSRPath.getFileName().toString();
        String filteredFileName = removeCSRStringFromFileName(fileName);
        final Path outputPath = Paths.get(params2values.get(ActionParams.OUTPUT_FOLDER.getName()));
        final Path outputFilePath = Paths.get(outputPath.toString(), filteredFileName);

        LOG.info("Opening keystore...");

        CredentialPropertiesProvider credPropsProvider = new CredentialPropertiesProvider();
        final CredentialProperties tenantCredentialProperties =
            credPropsProvider.getTenantCredentialPropertiesFromClassPath();

        char[] password = PasswordReaderUtils.readPasswordFromConsole();

        CryptoAPIScytlKeyStore platformRootKeyStore =
            KeyStoreLoader.loadPlatformRootKeyStore(platformRootKeyStorePath, password);

        PrivateKey platformRootPrivateKey =
            getPlatformRootPrivateKey(platformRootKeyStore, tenantCredentialProperties, password);
        X509Certificate platformRootCA =
            (X509Certificate) getPlatformRootCA(platformRootKeyStore, tenantCredentialProperties);

        LOG.info("Loading CSR...");

        CSRLoader csrLoader = new CSRLoader();
        JcaPKCS10CertificationRequest csr = csrLoader.getCSRFromPEM(tenantCSRPath);

        LOG.info("Signing tenant certificate...");

        CertificateRequestSigner certificateRequestSigner = new CertificateRequestSigner();
        CSRSigningInputProperties csrSingingPropertyInputs =
            certificateRequestSigner.getCsrSingingInputProperties(tenantCredentialProperties);
        CryptoAPIX509Certificate tenantCACert =
            certificateRequestSigner.signCSR(platformRootCA, platformRootPrivateKey, csr, csrSingingPropertyInputs);

        LOG.info("Persisting the tenant certificate...");

        FileUtils.createOutputDirectory(outputPath);
        CertificatePersister.saveCertificate(tenantCACert, outputFilePath);

        LOG.info("Deleting password from memory...");

        passwordCleaner.clean(password);

        LOG.info("Process finished successfully");
    }

    private String removeCSRStringFromFileName(final String fileName) {
        return fileName.replace("_CSR", "");
    }

    private PrivateKey getPlatformRootPrivateKey(final CryptoAPIScytlKeyStore platformRootKeyStore,
            final CredentialProperties properties, final char[] password) {

        KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password);
        PrivateKey privateKey = null;

        String alias = properties.getAlias().get(CertificatePropertiesConstants.PRIVATE_KEY_ALIAS);

        try {
            privateKey = platformRootKeyStore.getPrivateKeyEntry(alias, passwordProtection.getPassword());
        } catch (GeneralCryptoLibException e) {
            throw new CertificateCommandException("The private key could not be retrieved", e);
        }

        return privateKey;
    }

    public Certificate getPlatformRootCA(final CryptoAPIScytlKeyStore platformRootKeyStore,
            final CredentialProperties properties) {

        Certificate[] certificateChain = null;

        String alias = properties.getAlias().get(CertificatePropertiesConstants.PRIVATE_KEY_ALIAS);

        try {
            certificateChain = platformRootKeyStore.getCertificateChain(alias);
        } catch (GeneralCryptoLibException e) {
            throw new CertificateCommandException("The private key could not be retrieved", e);
        }

        return certificateChain[0];
    }
}
