package com.tuoheng.config.http;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpClientConnectionManager;
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.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class RestTemplateConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateConfig.class);

    @Bean
    public RestTemplate restTemplate(RestProperties restProperties) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory(restProperties));
        // 获取restTemplate中的转换器集合
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        // 遍历转换器集合，找到对应的StringHttpMessageConverter转换器
        for (HttpMessageConverter<?> converter : converterList) {
            if(converter.getClass() == StringHttpMessageConverter.class){
                converterList.remove(converter);
                break;
            }
        }
        // 添加新的StringHttpMessageConverter转换器，并设置字符集为UTF-8
        converterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        // 添加拦截器
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        // interceptors.add(new DefaultClientHttpRequestInterceptor());
        interceptors.add(new HeadClientHttpRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    }

    private ClientHttpRequestFactory clientHttpRequestFactory(RestProperties restProperties) {
        // httpClientBuilder配置构架器
        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        // 设置重试次数，此处注意，如果使用无参构造，重试次数为3。this(3, false);
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(restProperties.getRetryCount(), restProperties.getRequestSentRetryEnabled()));
        // 设置保持长连接
        httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
        // 使用连接池
        httpClientBuilder.setConnectionManager(poolingConnectionManager(restProperties));
        // 获取httpClient
        CloseableHttpClient httpClient = httpClientBuilder.build();
        // 配置HttpClient的对应工厂HttpComponentsClientHttpRequestFactory
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(restProperties.getConnectTimeout());
        factory.setReadTimeout(restProperties.getReadTimeout());
        factory.setConnectionRequestTimeout(restProperties.getConnectionRequestTimeout());
        return factory;
    }

    private HttpClientConnectionManager poolingConnectionManager(RestProperties restProperties) {
        // 注册http和https请求
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register(RestProperties.HTTP, PlainConnectionSocketFactory.getSocketFactory())
                .register(RestProperties.HTTPS, createSSLConn())
                .build();
        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(registry);
        // 最大连接数
        poolingConnectionManager.setMaxTotal(restProperties.getMaxTotal());
        // 每个主机的并发
        poolingConnectionManager.setDefaultMaxPerRoute(restProperties.getMaxPerRoute());
        // 空闲连接过期时间
        poolingConnectionManager.setValidateAfterInactivity(restProperties.getValidateAfterInactivity());
        return poolingConnectionManager;
    }

    private SSLConnectionSocketFactory createSSLConn() {
        SSLConnectionSocketFactory sslsf = null;
        try
        {
            SSLContext sslContext = new SSLContextBuilder()
                    // 不检查证书
                    .loadTrustMaterial(null, (X509Certificate[] chain, String authType) -> true)
                    .build();
            sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); // 不检查hostname
        } catch (GeneralSecurityException e){
            LOGGER.error("restTemplate开启SSL校验失败, error:{}", e);
        }
        return sslsf;
    }


    /**
     * 自定义的重试策略，需要的时候使用
     */
    private void customHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled){
        // 请求失败时,进行请求重试
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int currentRetryCount, HttpContext httpContext) {
                if(!requestSentRetryEnabled){
                    return false;
                }
                if (currentRetryCount > retryCount){
                    // 重试超过3次,放弃请求
                    LOGGER.error("retry has more than 3 time, give up request");
                    return false;
                }
                if (e instanceof NoHttpResponseException){
                    // 服务器没有响应,可能是服务器断开了连接,应该重试
                    LOGGER.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException){
                    // SSL握手异常
                    LOGGER.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException){
                    // 超时
                    LOGGER.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException){
                    // 服务器不可达
                    LOGGER.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException){
                    // 连接超时
                    LOGGER.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException){
                    LOGGER.error("SSLException");
                    return false;
                }
                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)){
                    // 如果请求不是关闭连接的请求
                    return true;

                }
                return false;
            }
        };
    }
}
