package com.hdkj.lt.core.rest.config;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.context.config.annotation.RefreshScope;
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.client.Netty4ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @Description: HttpClient配置类，配置了不同策略的RestTemplate
 * @Author: wenhao
 * @Create: 2022/07/07 15:45
 * @Version: 1.0
 */
@Slf4j
@RefreshScope
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RestTemplate.class, CloseableHttpClient.class})
public class HttpClientConfig {

    public static final String ASYNC_REST_TEMPLATE = "asyncRestTemplate";

    public static final String CONN_POOL_REST_TEMPLATE = "connPoolRestTemplate";

    public static final String CLOUD_REST_TEMPLATE = "cloudRestTemplate";

    public static final String CLIENT_HTTP_REQUEST_FACTORY = "clientHttpRequestFactory";

    @Resource
    private HttpClientPoolProperties poolConfigProperties;

    /**
     * 默认的RestTemplate
     *
     */
    @Bean(name = "simpleRestTemplate")
    @Primary
    public RestTemplate simpleRestTemplate() {
        SimpleClientHttpRequestFactory hf = new SimpleClientHttpRequestFactory();
        hf.setConnectTimeout(5*60*1000);
        hf.setReadTimeout(5*60*1000);
        return new RestTemplate(hf);
    }

//    /**
//     * 支持异步的RestTemplate（过时的）
//     *
//     * @param restTemplate
//     * @return
//     */
//    @Bean(name = ASYNC_REST_TEMPLATE)
//    @ConditionalOnMissingBean(AsyncRestTemplate.class)
//    public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
//        Netty4ClientHttpRequestFactory requestFactory = new Netty4ClientHttpRequestFactory();
//        requestFactory.setConnectTimeout(poolConfigProperties.connectTimeout);
//        requestFactory.setReadTimeout(poolConfigProperties.readTimeout);
//        return new AsyncRestTemplate(requestFactory, restTemplate);
//    }

    /**
     * 创建http客户端工厂
     *
     * @return
     */
    @Bean(name = CLIENT_HTTP_REQUEST_FACTORY)
    public ClientHttpRequestFactory clientHttpRequestFactory() {
        // maxTotal和maxPerRoute必须要配
        if (poolConfigProperties.getMaxTotal() <= 0) {
            throw new IllegalArgumentException("invalid maxTotal: " + poolConfigProperties.getMaxTotal());
        }
        if (poolConfigProperties.getMaxPerRoute() <= 0) {
            throw new IllegalArgumentException("invalid maxPerRoute: " + poolConfigProperties.getMaxPerRoute());
        }
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(this.httpClient());
        clientHttpRequestFactory.setConnectTimeout(poolConfigProperties.getConnectTimeout());
        clientHttpRequestFactory.setReadTimeout(poolConfigProperties.getReadTimeout());
        clientHttpRequestFactory.setConnectionRequestTimeout(poolConfigProperties.getConnectionRequestTimeout());
        return clientHttpRequestFactory;
    }

    /**
     * 支持连接池的RestTemplate
     *
     * @param requestFactory
     * @return
     */
    @Bean(name = CONN_POOL_REST_TEMPLATE)
    public RestTemplate connPoolRestTemplate(@Qualifier(CLIENT_HTTP_REQUEST_FACTORY) ClientHttpRequestFactory requestFactory) {
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 采用RestTemplate内部的MessageConverter，重新设置StringHttpMessageConverter字符集，解决中文乱码问题
        this.modifyDefaultCharset(restTemplate);
        // 设置错误处理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    /**
     * 支持微服务调用的RestTemplate
     *
     * @param requestFactory
     * @return
     */
    @LoadBalanced
    @Bean(name = CLOUD_REST_TEMPLATE)
    public RestTemplate cloudRestTemplate(@Qualifier(CLIENT_HTTP_REQUEST_FACTORY) ClientHttpRequestFactory requestFactory) {
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 采用RestTemplate内部的MessageConverter，重新设置StringHttpMessageConverter字符集，解决中文乱码问题
        this.modifyDefaultCharset(restTemplate);
        // 设置错误处理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    /**
     *
     * @return
     */
    @Bean(name = "simpleLongTimeRestTemplate")
    public RestTemplate simpleLongTimeRestTemplate() {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(this.httpClient());
        clientHttpRequestFactory.setConnectTimeout(poolConfigProperties.getConnectTimeout());
        clientHttpRequestFactory.setReadTimeout(60*60*1000);
        clientHttpRequestFactory.setConnectionRequestTimeout(poolConfigProperties.getConnectionRequestTimeout());
//        SimpleClientHttpRequestFactory hf = new SimpleClientHttpRequestFactory();
//        hf.setConnectTimeout(30*60*1000);
//        hf.setReadTimeout(30*60*1000);
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        // 采用RestTemplate内部的MessageConverter，重新设置StringHttpMessageConverter字符集，解决中文乱码问题
        this.modifyDefaultCharset(restTemplate);
        // 设置错误处理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    /**
     * 配置HttpClient
     *
     * @return
     */
    public HttpClient httpClient() {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        try {
            // 设置信任ssl访问
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
            httpClientBuilder.setSSLContext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    // 注册http和https请求
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();
            // 使用HttpClient连接池的方式配置（推荐），同时支持netty，okHttp以及其他http框架
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            // 最大连接数
            connectionManager.setMaxTotal(poolConfigProperties.getMaxTotal());
            // 同路由并发数
            connectionManager.setDefaultMaxPerRoute(poolConfigProperties.getMaxPerRoute());
            // 配置连接池
            httpClientBuilder.setConnectionManager(connectionManager);
            // 重试次数
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(poolConfigProperties.getRetryCount(), true));
            // 设置默认请求头
            List<Header> headers = this.getDefaultHeaders();
            httpClientBuilder.setDefaultHeaders(headers);
            // 设置长连接保持策略
            httpClientBuilder.setKeepAliveStrategy(this.connectionKeepAliveStrategy());
            return httpClientBuilder.build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            log.error("初始化HTTP连接池出错", e);
        }
        return null;
    }

    /**
     * 配置长连接保持策略
     *
     * @return
     */
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, context) -> {
            // Honor 'keep-alive' header
            HeaderElementIterator headerElements = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (headerElements.hasNext()) {
                HeaderElement header = headerElements.nextElement();
                log.info("HeaderElement:{}", JSON.toJSONString(header));
                String headerName = header.getName();
                String headerValue = header.getValue();
                if (StringUtils.equalsIgnoreCase(HttpHeaders.TIMEOUT, headerName) && StringUtils.isNumeric(headerValue)) {
                    return NumberUtils.toLong(headerValue) * 1000;
                }
            }
            HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            // 如果请求目标地址，单独配置了长连接保持时间，使用该配置
            Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(poolConfigProperties.getKeepAliveTargetHost()).orElseGet(HashMap::new)
                    .entrySet().stream().filter(
                            e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
            // 否则使用默认长连接保持时间
            return any.map(e -> e.getValue() * 1000L).orElse(poolConfigProperties.getKeepAliveTimeout() * 1000L);
        };
    }

    /**
     * 设置默认的请求头
     *
     * @return
     */
    private List<Header> getDefaultHeaders() {
        return Lists.newArrayList(
                new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"),
                new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,deflate"),
                new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN"),
                new BasicHeader(HttpHeaders.CONNECTION, "Keep-Alive")
        );
    }

    /**
     * 修改默认的字符集
     *
     * @param restTemplate
     */
    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (Objects.nonNull(converterTarget)) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = Charset.forName(poolConfigProperties.getCharset());
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }

    @Data
    @Configuration
    @ConfigurationProperties(prefix = "lt.http-client.pool") //默认配置的优先级低于yml配置；如果yml配置不存在，会采用默认配置
    public static class HttpClientPoolProperties {

        private int maxTotal = 50; //最大连接数

        private int maxPerRoute = 100; //同路由的并发数

        private int retryCount = 3; //重试次数，默认3次

        private String charset = Charsets.UTF_8.toString(); //字符集，默认UTF-8

        private int connectTimeout = 3 * 1000; //连接超时，默认3s

        private int readTimeout = 30 * 1000; //客户端从服务端读取数据包的间隔超时时间，不是总读取时间，默认30s

        private int connectionRequestTimeout = 200; //从连接池获取连接的超时时间，不宜过长，单位ms

        private int keepAliveTimeout = 60; //长连接保持时间，单位s

        private Map<String, Integer> keepAliveTargetHost; //针对不同的地址，特别设置不同的长连接保持时间

    }

}
