/*
 * 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.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;

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.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.ov.config.tenant.CertificatePropertiesConstants;
import com.scytl.products.ov.config.tenant.exception.KeyStoreServiceException;
import com.scytl.products.ov.datapacks.beans.CredentialProperties;

@Service
public class KeyStoreService {

    /**
     * creates a keystore Object
     * 
     * @param keyPairForSigning
     *            - key pair generated for the keystore creation
     * @param credentialProperties
     *            - properties with the credentials
     * @param certificate
     *            - certificate to be included in the keystore
     * @param password
     * @return
     */
    public CryptoAPIScytlKeyStore createTenantKeyStore(final KeyPair keyPairForSigning,
            final CredentialProperties credentialProperties, final CryptoAPIX509Certificate certificate,
            final char[] password) {

        CryptoScytlKeyStoreWithPBKDF tenantRootKeystore = null;
        try {
            final ScytlKeyStoreService storesService = new ScytlKeyStoreService();
            final CryptoScytlKeyStoreWithPBKDF keyStore = (CryptoScytlKeyStoreWithPBKDF) storesService.createKeyStore();
            tenantRootKeystore = setPrivateKeyToKeystore(keyStore,
                credentialProperties.getAlias().get(CertificatePropertiesConstants.PRIVATE_KEY_ALIAS),
                keyPairForSigning.getPrivate(), password, certificate);
        } catch (GeneralCryptoLibException e) {
            throw new KeyStoreServiceException("An error occurred creating the tenant keystore", e);
        }

        return tenantRootKeystore;
    }

    /**
     * Set the private key to the keystore
     * 
     * @param keystore
     * @param alias
     * @param privateKey
     * @param keyStorePassword
     * @param certs
     * @return
     * @throws GeneralCryptoLibException
     */
    public CryptoScytlKeyStoreWithPBKDF setPrivateKeyToKeystore(final CryptoScytlKeyStoreWithPBKDF keystore,
            final String alias, final PrivateKey privateKey, final char[] keyStorePassword,
            final CryptoAPIX509Certificate... certs) throws GeneralCryptoLibException {

        final Certificate[] chain = new Certificate[certs.length];

        for (int i = 0; i < certs.length; i++) {
            chain[i] = com.scytl.cryptolib.primitives.primes.utils.PemUtils
                .certificateFromPem(new String(certs[i].getPemEncoded()));
        }

        keystore.setPrivateKeyEntry(alias, privateKey, keyStorePassword, chain);

        return keystore;
    }

    public void saveKeyStore(final CryptoAPIScytlKeyStore keyStore, final Path path, final char[] keyStorePassword) {

        try (final OutputStream out = Files.newOutputStream(path)) {
            keyStore.store(out, keyStorePassword);
        } catch (IOException | GeneralCryptoLibException e) {
            throw new KeyStoreServiceException("An error occurred persisting the keystore.", e);
        }
    }

    /**
     * Updates the keystore with a chain of certificates
     * 
     * @param password
     *            - password to open the keystore
     * @param keystorePath
     *            - path to the keystore
     * @param properties
     *            - configuration properties
     * @param certificates
     *            - chain of certificates
     * @return
     */
    public CryptoScytlKeyStoreWithPBKDF updateKeyStore(char[] password, Path keystorePath,
            CredentialProperties properties, Certificate[] certificates) {

        CryptoScytlKeyStoreWithPBKDF result = null;
        KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password);
        CryptoAPIScytlKeyStore cryptoScytlKeyStore;

        try (InputStream keystoreInputStream = Files.newInputStream(keystorePath)) {
            final ScytlKeyStoreService scytlKeyStoreService = new ScytlKeyStoreService();
            cryptoScytlKeyStore = scytlKeyStoreService.loadKeyStore(keystoreInputStream, passwordProtection);

            result = (CryptoScytlKeyStoreWithPBKDF) scytlKeyStoreService.createKeyStore();
            String alias = properties.getAlias().get(CertificatePropertiesConstants.PRIVATE_KEY_ALIAS);

            PrivateKey privateKey = cryptoScytlKeyStore.getPrivateKeyEntry(alias, passwordProtection.getPassword());

            result.setPrivateKeyEntry(alias, privateKey, passwordProtection.getPassword(), certificates);
        } catch (IOException | GeneralCryptoLibException e) {
            throw new KeyStoreServiceException("An error occurred updating the keystore", e);
        }

        return result;

    }

    public CryptoAPIScytlKeyStore loadKeyStore(final Path keystorePath, final char[] password) {
        KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password);
        CryptoAPIScytlKeyStore cryptoScytlKeyStore = null;
        try (InputStream keystoreInputStream = Files.newInputStream(keystorePath)) {
            ScytlKeyStoreService scytlKeyStoreService = new ScytlKeyStoreService();
            cryptoScytlKeyStore = scytlKeyStoreService.loadKeyStore(keystoreInputStream, passwordProtection);
        } catch (GeneralCryptoLibException | IOException e) {
            throw new KeyStoreServiceException("The given keystore cannot be opened with the given password", e);
        }
        return cryptoScytlKeyStore;
    }

}
