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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;

import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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.cryptolib.scytl.keystore.factory.CryptoScytlKeyStoreWithPBKDF;
import com.scytl.products.ov.config.tenant.CertificatePropertiesConstants;
import com.scytl.products.ov.config.tenant.ValidationException;
import com.scytl.products.ov.config.tenant.commands.GenerateTenantParametersHolder;
import com.scytl.products.ov.config.tenant.commands.UpdateTenantParametersHolder;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;
import com.scytl.products.ov.utils.PasswordCleaner;

@Service
public class ConfigTenantService {

    public static final String TENANTCA = "tenant_${tenantName}";

    public static final String TENANT_NAME_TAG = "${tenantName}";

    public static final String CSR_SUFFIX = "_CSR";

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

    @Autowired
    private CertificateService certificateService;

    @Autowired
    private KeyStoreService keyStoreService;

    @Autowired
    private ValidationService validationService;

    @Autowired
    private KeyPairService keyPairService;

    @Autowired
    private IOUtilsService ioUtilsService;

    private final PasswordCleaner passwordCleaner = new PasswordCleaner();

    public void generateCSRandKeystore(final char[] password, final GenerateTenantParametersHolder holder) {

        LOG.info("Generating keys for signing...");
        KeyPair keyPairForSigning = keyPairService.generateKeyPairForSigning();
        LOG.info("Generating the CSR...");

        final CredentialProperties credentialProperties = ioUtilsService.getCredentialProperties();

        PKCS10CertificationRequest csr = certificateService.createCSR(keyPairForSigning.getPublic(),
            keyPairForSigning.getPrivate(), credentialProperties, holder.getName());

        CryptoAPIX509Certificate keystoreCertificate =
            certificateService.generateCertificate(keyPairForSigning, credentialProperties, holder.getName());

        CryptoAPIScytlKeyStore tenantKeyStore = keyStoreService.createTenantKeyStore(keyPairForSigning,
            credentialProperties, keystoreCertificate, password);

        LOG.info("Persisting the keystore and the certificate...");

        Path outputPath = Paths.get(holder.getOutput());
        ioUtilsService.createOutputDirectory(outputPath);

        String tenantFileName = TENANTCA.replace(TENANT_NAME_TAG, holder.getName());
        certificateService.saveCSR(csr, Paths.get(holder.getOutput(), tenantFileName + CSR_SUFFIX + Constants.PEM));

        keyStoreService.saveKeyStore(tenantKeyStore, Paths.get(holder.getOutput(), tenantFileName + Constants.SKS),
            password);

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

        passwordCleaner.clean(password);

        LOG.info("Deleted password from memory.");
    }

    /**
     * Updates the tenantKeyStore with the provided chain of certificates
     *
     * @param password
     * @param holder
     * @throws IOException
     * @throws GeneralCryptoLibException
     */
    public void updateTenantKeystore(final char[] password, final UpdateTenantParametersHolder holder)
            throws IOException, GeneralCryptoLibException, ValidationException, CertificateException {

        Path tenantCertificatePath =
            Paths.get(Paths.get(holder.getTenantCertificatePath()).toFile().getCanonicalPath());
        Path rootCACertificatePath =
            Paths.get(Paths.get(holder.getRootCACertificatePath()).toFile().getCanonicalPath());
        final CredentialProperties credentialProperties = ioUtilsService.getCredentialProperties();

        Path keyStorePath = Paths.get(Paths.get(holder.getKeyStorePath()).toFile().getCanonicalPath());
        Certificate platformRootCertificate = com.scytl.cryptolib.primitives.primes.utils.PemUtils
            .certificateFromPem(new String(Files.readAllBytes(rootCACertificatePath), StandardCharsets.UTF_8));
        Certificate tenantCertificate = com.scytl.cryptolib.primitives.primes.utils.PemUtils
            .certificateFromPem(new String(Files.readAllBytes(tenantCertificatePath), StandardCharsets.UTF_8));
        Certificate[] chain = {tenantCertificate, platformRootCertificate };
        validationService.validateChain(platformRootCertificate, tenantCertificate);

        CryptoScytlKeyStoreWithPBKDF keyStoreWithPBKDF =
            keyStoreService.updateKeyStore(password, keyStorePath, credentialProperties, chain);

        String alias = credentialProperties.getAlias().get(CertificatePropertiesConstants.PRIVATE_KEY_ALIAS);
        validationService.validateKeysInCertificates(tenantCertificate, keyStoreWithPBKDF, alias, password);

        keyStoreService.saveKeyStore(keyStoreWithPBKDF, keyStorePath, password);

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

        passwordCleaner.clean(password);

        LOG.info("Deleted password from memory.");
    }
}
