package com.jkcredit.identity.config;

import com.jkcredit.identity.config.properties.HttpClientProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * 国密Https客户端
 *
 * 同时支持HTTP和HTTPS
 * 使用连接池重复使用连接并节省创建连接的开销
 * 启动空闲连接监视器以持续清理过时的连接
 */
@Slf4j
@Configuration
public class HttpClientConfiguration {
    final CloseableHttpClient httpClient;

    public HttpClientConfiguration(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setHttpClient(httpClient);
        return clientHttpRequestFactory;
    }

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("poolScheduler");
        scheduler.setPoolSize(50);
        return scheduler;
    }
    @Bean
    public RestTemplate gmRestTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(factory);
        restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
            // log the http request
            log.info("URI: {}", request.getURI());
            log.info("HTTP Method: {}", request.getMethodValue());
            log.info("HTTP Headers: {}", request.getHeaders());
            return execution.execute(request, body);
        }));
        return restTemplate;
    }


    @Slf4j
    @EnableScheduling
    @Configuration
    @EnableConfigurationProperties(HttpClientProperties.class)
    public static class HttpClientConfig {

        private final HttpClientProperties httpClientProperties;

        public HttpClientConfig(HttpClientProperties httpClientProperties) {
            this.httpClientProperties = httpClientProperties;
        }

        @Bean
        public PoolingHttpClientConnectionManager poolingConnectionManager() {
            Security.insertProviderAt(new cn.gmssl.jce.provider.GMJCE(), 1);
            Security.insertProviderAt(new cn.gmssl.jsse.provider.GMJSSE(), 2);

            SSLConnectionSocketFactory sslsf = null;

            try {
                TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
                SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
                sslsf = new SSLConnectionSocketFactory(sslContext,
                        new String[]{"GMSSLv1.1"}, new String[]{"ECC_SM4_SM3"},
                        new NoopHostnameVerifier());
            } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
                log.error("连接池管理器初始化失败，原因是 " + e.getMessage(), e);
                throw new RuntimeException();
            }

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                    .<ConnectionSocketFactory>create().register("https", sslsf)
                    .register("http", new PlainConnectionSocketFactory())
                    .build();

            PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolingConnectionManager.setMaxTotal(httpClientProperties.getMaxTotalConnections());
            return poolingConnectionManager;
        }

        @Bean
        public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
            return (response, context) -> {
                HeaderElementIterator it = new BasicHeaderElementIterator
                        (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();

                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        return Long.parseLong(value) * 1000;
                    }
                }
                return httpClientProperties.getDefaultKeepAliveTimeMillis();
            };
        }

        @Bean
        public CloseableHttpClient httpClient() {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(httpClientProperties.getRequestTimeout())
                    .setConnectTimeout(httpClientProperties.getConnectTimeout())
                    .setSocketTimeout(httpClientProperties.getSocketTimeout()).build();

            return HttpClients.custom()
                    .setDefaultRequestConfig(requestConfig)
                    .setConnectionManager(poolingConnectionManager())
                    .setKeepAliveStrategy(connectionKeepAliveStrategy())
                    .build();
        }

        @Bean
        public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
            return new Runnable() {
                @Override
                @Scheduled(fixedDelay = 10000)
                public void run() {
                    try {
                        if (connectionManager != null) {
                            log.trace("运行IdleConnectionMonitor-关闭过期和空闲连接...");
                            connectionManager.closeExpiredConnections();
                            connectionManager.closeIdleConnections(httpClientProperties.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS);
                        } else {
                            log.trace("运行IdleConnectionMonitor-未初始化Http客户端连接管理器");
                        }
                    } catch (Exception e) {
                        log.error("运行IdleConnectionMonitor-发生异常. msg={}, e={}", e.getMessage(), e);
                    }
                }
            };
        }
    }
}
