package mousio.etcd4j.security;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import mousio.client.exceptions.SecurityContextException;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

/**
 * Easy-to-use SslContext builder for etcd clients
 */
public final class SecurityContextBuilder {
    public static final String KEYSTORE_JKS = "JKS";

    /**
     * Builds SslContext using a protected keystore file. Adequate for non-mutual TLS connections.
     *
     * @param keystorePath Path for keystore file
     * @param keystorePassword Password for protected keystore file
     * @return SslContext ready to use
     * @throws SecurityContextException for any troubles building the SslContext
     */
    public static SslContext forKeystore(String keystorePath, String keystorePassword)
            throws SecurityContextException {
        return forKeystore(keystorePath, keystorePassword, "SunX509");
    }

    /**
     * Builds SslContext using protected keystore file overriding default key manger algorithm. Adequate for non-mutual TLS connections.
     *
     * @param keystorePath Path for keystore file
     * @param keystorePassword Password for protected keystore file
     * @param keyManagerAlgorithm Algorithm for keyManager used to process keystorefile
     * @return SslContext ready to use
     * @throws SecurityContextException for any troubles building the SslContext
     */
    public static SslContext forKeystore(String keystorePath, String keystorePassword, String keyManagerAlgorithm)
            throws SecurityContextException {

        try {
            return forKeystore(new FileInputStream(keystorePath), keystorePassword, keyManagerAlgorithm);
        } catch (Exception e) {
            throw new SecurityContextException(e);
        }
    }

    /**
     * Builds SslContext using protected keystore file overriding default key manger algorithm. Adequate for non-mutual TLS connections.
     *
     * @param keystore Keystore inputstream (file, binaries, etc)
     * @param keystorePassword Password for protected keystore file
     * @param keyManagerAlgorithm Algorithm for keyManager used to process keystorefile
     * @return SslContext ready to use
     * @throws SecurityContextException for any troubles building the SslContext
     */
    public static SslContext forKeystore(InputStream keystore, String keystorePassword, String keyManagerAlgorithm)
            throws SecurityContextException {

        try {
            final KeyStore ks = KeyStore.getInstance(KEYSTORE_JKS);
            final KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerAlgorithm);

            ks.load(keystore, keystorePassword.toCharArray());
            kmf.init(ks, keystorePassword.toCharArray());

            SslContextBuilder ctxBuilder = SslContextBuilder.forClient().keyManager(kmf);
            return ctxBuilder.build();
        } catch (Exception e) {
            throw new SecurityContextException(e);
        }
    }

    /**
     * Builds SslContext using protected keystore and truststores. Adequate for mutual TLS connections.
     * @param keystorePath Path for keystore file
     * @param keystorePassword Password for protected keystore file
     * @param truststorePath Path for truststore file
     * @param truststorePassword Password for protected truststore file
     * @return SslContext ready to use
     * @throws SecurityContextException
     */
    public static SslContext forKeystoreAndTruststore(String keystorePath, String keystorePassword, String truststorePath, String truststorePassword)
            throws SecurityContextException {
        return forKeystoreAndTruststore(keystorePath, keystorePassword, truststorePath, truststorePassword, "SunX509");
    }

    /**
     * Builds SslContext using protected keystore and truststores. Adequate for mutual TLS connections.
     * @param keystorePath Path for keystore file
     * @param keystorePassword Password for protected keystore file
     * @param truststorePath Path for truststore file
     * @param truststorePassword Password for protected truststore file
     * @param keyManagerAlgorithm Algorithm for keyManager used to process keystorefile
     * @return SslContext ready to use
     * @throws SecurityContextException
     */
    public static SslContext forKeystoreAndTruststore(String keystorePath, String keystorePassword, String truststorePath, String truststorePassword, String keyManagerAlgorithm)
            throws SecurityContextException {

        try {
            return forKeystoreAndTruststore(new FileInputStream(keystorePath), keystorePassword, new FileInputStream(truststorePath), truststorePassword, keyManagerAlgorithm);
        } catch (Exception e) {
            throw new SecurityContextException(e);
        }
    }

    /**
     * Builds SslContext using protected keystore and truststores, overriding default key manger algorithm. Adequate for mutual TLS connections.
     * @param keystore Keystore inputstream (file, binaries, etc)
     * @param keystorePassword Password for protected keystore file
     * @param truststore Truststore inputstream (file, binaries, etc)
     * @param truststorePassword Password for protected truststore file
     * @param keyManagerAlgorithm Algorithm for keyManager used to process keystorefile
     * @return SslContext ready to use
     * @throws SecurityContextException
     */
    public static SslContext forKeystoreAndTruststore(InputStream keystore, String keystorePassword, InputStream truststore, String truststorePassword, String keyManagerAlgorithm)
            throws SecurityContextException {
        try {
            final KeyStore ks = KeyStore.getInstance(KEYSTORE_JKS);
            final KeyStore ts = KeyStore.getInstance(KEYSTORE_JKS);

            final KeyManagerFactory keystoreKmf = KeyManagerFactory.getInstance(keyManagerAlgorithm);
            final TrustManagerFactory truststoreKmf = TrustManagerFactory.getInstance(keyManagerAlgorithm);

            ks.load(keystore, keystorePassword.toCharArray());
            ts.load(truststore, truststorePassword.toCharArray());

            keystoreKmf.init(ks, keystorePassword.toCharArray());
            truststoreKmf.init(ts);

            SslContextBuilder ctxBuilder = SslContextBuilder.forClient().keyManager(keystoreKmf);
            ctxBuilder.trustManager(truststoreKmf);

            return ctxBuilder.build();
        } catch (Exception e) {
            throw new SecurityContextException(e);
        }
    }
}