/*
 * See LICENSE for licensing and NOTICE for copyright.
 */
package edu.vt.middleware.app;

import java.io.File;
import java.security.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;

import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.cryptacular.bean.KeyStoreFactoryBean;
import org.cryptacular.io.ClassPathResource;
import org.cryptacular.io.FileResource;
import org.cryptacular.io.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * Application entry point.
 *
 * @author Marvin S. Addison
 */
@SpringBootApplication
public class Application {

    /** Logger instance. */
    private final Logger logger = LoggerFactory.getLogger(Application.class);

    /** Application HTTP client connection pool size. */
    @Value("${application.httpclient.pool.size:10}")
    private int httpClientPoolSize;

    /** Application HTTP client keepalive duration in seconds. */
    @Value("${application.httpclient.keepalive:120}")
    private int httpClientKeepAlive;

    /** Application keystore path. */
    @Value("${application.keystore.path}")
    private String keystorePath;

    /** Application keystore type. */
    @Value("${application.keystore.type}")
    private String keystoreType;

    /** Application keystore password. */
    @Value("${application.keystore.password}")
    private String keystorePassword;

    /** Keystore alias for application client credential. */
    @Value("${application.keystore.entry.app-name}")
    private String applicationKeyAlias;


    public static void main(String[] args) {
        LoggerFactory.getLogger(Application.class).info("Starting application");
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new HttpComponentsClientHttpRequestFactory(createHttpClient(applicationKeyAlias)));
    }

    private static Resource makeResource(final String path) {
        if (path.startsWith(FILE_RESOURCE_PREFIX)) {
            return new FileResource(new File(path.substring(FILE_RESOURCE_PREFIX.length())));
        } else if (path.startsWith(CLASSPATH_RESOURCE_PREFIX)) {
            return new ClassPathResource(path.substring(CLASSPATH_RESOURCE_PREFIX.length()));
        }
        // Assume a path without a known prefix is a file
        return new FileResource(new File(path));
    }

    private HttpClient createHttpClient(final String keyAlias) {
        logger.info("Creating HTTP client using keystore={} and alias={}", keystorePath, keyAlias);
        final KeyStore trustStore = new KeyStoreFactoryBean(
            makeResource("classpath:/truststore.jks"), "JKS", "changeit").newInstance();
        final KeyStore keyStore = new KeyStoreFactoryBean(
            makeResource(keystorePath), keystoreType, keystorePassword).newInstance();
        final SSLContext sslContext;
        try {
            sslContext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, keystorePassword.toCharArray(), (aliases, socket) -> keyAlias)
                .loadTrustMaterial(trustStore, (x509Certificates, s) -> false)
                .build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
            throw new IllegalStateException("Error loading key or trust material", e);
        }
        final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
            sslContext,
            new String[] { "TLSv1.2" },
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslSocketFactory)
            .build();
        final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setMaxTotal(httpClientPoolSize);
        connectionManager.setDefaultMaxPerRoute(httpClientPoolSize);
        return HttpClients.custom()
            .setSSLSocketFactory(sslSocketFactory)
            .setConnectionManager(connectionManager)
            .build();
    }
}