package eu.eudat.configurations;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.nio.reactor.IOReactorExceptionHandler;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

/**
 * Created by ikalyvas on 7/5/2018.
 */
@Configuration
public class ElasticSearchConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(ElasticSearchConfiguration.class);

    private Environment environment;

    @Autowired
    public ElasticSearchConfiguration(Environment environment) {
        this.environment = environment;
    }

    @Bean(destroyMethod = "close")
    public RestHighLevelClient client() throws Exception {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(this.environment.getProperty("elasticsearch.username"), this.environment.getProperty("elasticsearch.password")));

        try {
            DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();
            ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {
                @Override
                public boolean handle(IOException e) {
                    logger.warn("System may be unstable: IOReactor encountered a checked exception : " + e.getMessage(), e);
                    return true; // Return true to note this exception as handled, it will not be re-thrown
                }

                @Override
                public boolean handle(RuntimeException e) {
                    logger.warn("System may be unstable: IOReactor encountered a runtime exception : " + e.getMessage(), e);
                    return true; // Return true to note this exception as handled, it will not be re-thrown
                }
            });

            RestHighLevelClient client;
            if(this.environment.getProperty("elasticsearch.usingssl", Boolean.class)){

                Path caCertificatePath = Paths.get(this.environment.getProperty("elasticsearch.certPath"));
                CertificateFactory factory =
                        CertificateFactory.getInstance("X.509");
                Certificate trustedCa;
                try (InputStream is = Files.newInputStream(caCertificatePath)) {
                    trustedCa = factory.generateCertificate(is);
                }
                KeyStore trustStore = KeyStore.getInstance("pkcs12");
                trustStore.load(null, null);
                trustStore.setCertificateEntry("ca", trustedCa);

                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                tmf.init(trustStore);

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, tmf.getTrustManagers(), null);

                client = new RestHighLevelClient(
                        RestClient.builder(
                                new HttpHost(this.environment.getProperty("elasticsearch.host"),
                                        Integer.parseInt(this.environment.getProperty("elasticsearch.port")), "https"))
                                .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
                                        .setDefaultCredentialsProvider(credentialsProvider).setSSLContext(sslContext)));
            }
            else {
                 client = new RestHighLevelClient(
                        RestClient.builder(
                                new HttpHost(this.environment.getProperty("elasticsearch.host"),
                                        Integer.parseInt(this.environment.getProperty("elasticsearch.port")), "http"))
                                .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
                                        .setDefaultCredentialsProvider(credentialsProvider).setConnectionManager(new PoolingNHttpClientConnectionManager(ioReactor))));
            }
            return client;
        }catch (IOReactorException ex) {
            throw new RuntimeException(ex);
        }
    }
}
