package com.seezoon.framework.component;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import com.seezoon.framework.properties.SeezoonProperties;

import lombok.RequiredArgsConstructor;

/**
 * 优化原生无连接池问题
 *
 * @author hdf
 */
@Configuration
@RequiredArgsConstructor
public class RestTemplateCustomConfiguration {

    private final SeezoonProperties seezoonProperties;

    /**
     * 获取ssl的连接池,适合双向请求,微信开发常用,该对象较重请缓存使用
     *
     * <code>
     *     KeyStore ks = KeyStore.getInstance("PKCS12");
     * 	   ks.load(inputStream, mchId.toCharArray());
     * 	   // Trust own CA and all self-signed certs
     * 	   SSLContext sslcontext = SSLContexts.custom()
     * 					.loadKeyMaterial(ks, mchId.toCharArray())
     * 					.build();
     * </code>
     *
     * @return
     */
    public RestTemplate getSslRestTemplate(SSLContext sslContext) {
        Assert.notNull(sslContext, "sslContext must not null");
        ClientHttpRequestFactory factory = clientHttpRequestFactory(sslContext);
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

    /**
     * 优化默认连接池
     *
     * @return
     */
    @Primary
    @Bean
    public RestTemplate restTemplate() {
        ClientHttpRequestFactory factory = clientHttpRequestFactory(null);
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

    public HttpClientConnectionManager poolingConnectionManager() {
        // 自定义连接池的话这个更准确，自动已连接池参数需要都在连接池上设置,默认池子 {@link org.apache.http.impl.client.HttpClientBuilder#build}
        // 自定义ssl 策略时候,请查看该方法构造函数
        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();

        poolingConnectionManager.setMaxTotal(seezoonProperties.getHttp().getMaxTotal()); // 连接池最大连接数
        poolingConnectionManager.setDefaultMaxPerRoute(seezoonProperties.getHttp().getMaxPerRoute()); // 每个主机的并发
        // 长连接
        poolingConnectionManager.setDefaultSocketConfig(SocketConfig.custom()
            .setSoTimeout(seezoonProperties.getHttp().getSocketTimeout()).setSoKeepAlive(true).build());
        // 连接不活跃多久检查毫秒 并不是100 % 可信
        poolingConnectionManager.setValidateAfterInactivity(seezoonProperties.getHttp().getValidateAfterInactivity());
        // 空闲扫描线程
        HttpClientIdleConnectionMonitor.registerConnectionManager(poolingConnectionManager,
            seezoonProperties.getHttp().getIdleTimeToDead(), seezoonProperties.getHttp().getIdleScanTime());
        return poolingConnectionManager;
    }

    public HttpClientBuilder httpClientBuilder(SSLContext sslContext) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 设置HTTP连接管理器
        httpClientBuilder.setConnectionManager(poolingConnectionManager());
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(seezoonProperties.getHttp().getConnectionRequestTimeout())// 获取连接等待时间
            .setConnectTimeout(seezoonProperties.getHttp().getConnectTimeout())// 连接超时
            .setSocketTimeout(seezoonProperties.getHttp().getSocketTimeout())// 获取数据超时
            .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig).setSSLContext(sslContext)
            .setUserAgent(seezoonProperties.getHttp().getUserAgent()).disableContentCompression()
            // 收动设置连接池 这个在连接池那里设置，查看该方法注释该,为了避免版本升级导致错误，也冗余设置
            .setConnectionTimeToLive(seezoonProperties.getHttp().getConnTimeToLive(), TimeUnit.MILLISECONDS)// 连接最大存活时间
            // 默认重试次数为0，POST 不重试
            .setRetryHandler(new StandardHttpRequestRetryHandler(seezoonProperties.getHttp().getRetyTimes(), false));
        return httpClientBuilder;
    }

    public ClientHttpRequestFactory clientHttpRequestFactory(SSLContext sslContext) {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setHttpClient(httpClientBuilder(sslContext).build());
        return clientHttpRequestFactory;
    }

    /**
     * 来自优秀开源代码
     *
     * @see <a>https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/main/java/com/aliyun/oss/common/comm/IdleConnectionReaper.java</a>
     */
    public static final class HttpClientIdleConnectionMonitor extends Thread {

        private static final ArrayList<HttpClientConnectionManager> CONNECTION_MANAGERS = new ArrayList<>();
        private static Logger logger = LoggerFactory.getLogger(HttpClientIdleConnectionMonitor.class);
        private static long idleScanTime;
        private static HttpClientIdleConnectionMonitor instance;

        private static long idleConnectionTime;

        private volatile boolean shuttingDown;

        private HttpClientIdleConnectionMonitor(long idleConnectionTime, long idleScanTime) {
            super("Connection Manager Monitor");
            HttpClientIdleConnectionMonitor.idleConnectionTime = idleConnectionTime;
            HttpClientIdleConnectionMonitor.idleScanTime = idleScanTime;
            setDaemon(true);
        }

        /**
         * 如果 HttpClientIdleConnectionMonitor 已存在则设置的两个时间参数不生效
         *
         * @param connectionManager
         * @param idleConnectionTime
         * @param idleScanTime
         * @return
         */
        public static synchronized boolean registerConnectionManager(HttpClientConnectionManager connectionManager,
            long idleConnectionTime, long idleScanTime) {
            if (instance == null) {
                instance = new HttpClientIdleConnectionMonitor(idleConnectionTime, idleScanTime);
                instance.start();
            }
            return CONNECTION_MANAGERS.add(connectionManager);
        }

        public static synchronized boolean removeConnectionManager(HttpClientConnectionManager connectionManager) {
            boolean b = CONNECTION_MANAGERS.remove(connectionManager);
            if (CONNECTION_MANAGERS.isEmpty()) {
                shutdown();
            }
            return b;
        }

        public static synchronized boolean shutdown() {
            if (instance != null) {
                instance.markShuttingDown();
                instance.interrupt();
                CONNECTION_MANAGERS.clear();
                instance = null;
                return true;
            }
            return false;
        }

        public static synchronized int size() {
            return CONNECTION_MANAGERS.size();
        }

        public static synchronized void setIdleConnectionTime(long idletime) {
            idleConnectionTime = idletime;
        }

        private void markShuttingDown() {
            shuttingDown = true;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            while (true) {
                if (shuttingDown) {
                    logger.debug("Shutting down Connection Manager Monitor thread.");
                    return;
                }

                try {
                    Thread.sleep(idleScanTime);
                } catch (InterruptedException e) {
                }

                try {
                    List<HttpClientConnectionManager> connectionManagers = null;
                    synchronized (HttpClientIdleConnectionMonitor.class) {
                        connectionManagers = (List<
                            HttpClientConnectionManager>)HttpClientIdleConnectionMonitor.CONNECTION_MANAGERS.clone();
                    }
                    for (HttpClientConnectionManager connectionManager : connectionManagers) {
                        try {
                            connectionManager.closeExpiredConnections();
                            connectionManager.closeIdleConnections(idleConnectionTime, TimeUnit.MILLISECONDS);
                        } catch (Exception ex) {
                            logger.warn("Unable to close idle connections", ex);
                        }
                    }
                } catch (Throwable t) {
                    logger.debug("Connection Manager Monitor thread: ", t);
                }
            }
        }
    }
}
