package com.kiisoo.ic.common.utils.httpClientUtil;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
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.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
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.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

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.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * http 工具类
 * @author hc
 * @date 创建时间：2017年8月8日 下午6:20:33
 */
@Slf4j
public class HttpClientUtil {

	/**
	 * 	发送get请求
	 * @param url
	 * @return
	 */
	public static HttpResult httpGet(String url){
		HttpGet http = new HttpGet(url);
		return http(http);
	}

	/**
	 * 	发送get请求
	 * @param url
	 * @return
	 */
	public static HttpResult httpGet(String url,String[] params){
		HttpGet http = new HttpGet(getUrl(url,params));
		return http(http);
	}

	/**
	 * 	携带token发送get请求
	 * @param url
	 * @param token
	 * @return
	 */
	public static HttpResult httpGet(String url, String token){
		HttpGet http = new HttpGet(url);
		http.setHeader("token", token);
		return http(http);
	}

	/**
	 * 	携带token发送get请求
	 * @param url
	 * @param token
	 * @return
	 */
	public static HttpResult httpGet(String url, String token,String[] params){
		HttpGet http = new HttpGet(getUrl(url,params));
		http.setHeader("token", token);
		return http(http);
	}

	/**
	 * 	携带token发送post请求
	 * @param url
	 * @param json
	 * @param token
	 * @return
	 */
	public static HttpResult httpPost(String url, String json, String token){
		StringEntity paramEntity  = new StringEntity(json, Charset.forName("utf-8"));
		paramEntity.setContentType("application/json; charset=UTF-8");
		paramEntity.setContentEncoding("utf-8");

		HttpPost http = new HttpPost(url);
		http.setEntity(paramEntity);
		http.setHeader("token", token);

		return http(http);
	}

    /**
     * 	携带token发送post请求
     * @param url
     * @param json
     * @return
     */
    public static HttpResult httpPost(String url, String json){
        StringEntity paramEntity  = new StringEntity(json, Charset.forName("utf-8"));
        paramEntity.setContentType("application/json; charset=UTF-8");
        paramEntity.setContentEncoding("utf-8");

        HttpPost http = new HttpPost(url);
        http.setEntity(paramEntity);
        return http(http);
    }

	/**
	 * 	携带token发送post请求
	 * @param url
	 * @param token
	 * @return
	 */
	public static HttpResult httpPost(String url,String token,List<NameValuePair> nameValuePairList){

		StringEntity paramEntity = null;
		try {
			paramEntity = new UrlEncodedFormEntity(nameValuePairList, "UTF-8");
			paramEntity.setContentEncoding("utf-8");

		} catch (UnsupportedEncodingException e) {
			log.error("httpPost error",e);
		}

		HttpPost http = new HttpPost(url);
		http.setEntity(paramEntity);
		http.setHeader("token", token);
		return http(http);
	}

	/**
	 * 	拼接url
	 */
	public static String getUrl(String base, String... args) {
//		Object[] obj = new Object[10];
//		for(int i = 0; i< args.length; i++){
//			obj[i] = WordUtile.escapeQueryChars(args[i]);
//		}
		return String.format(base, (Object[])args);
	}

	
	/**
	 * 组织请求参数{参数名和参数值下标保持一致}
	 * @param params    参数名数组
	 * @param values    参数值数组
	 * @return 参数对象
	 */
	public static List<NameValuePair> getParams(Object[] params, Object[] values){
		/**
		 * 校验参数合法性
		 */
		boolean flag = params.length>0 && values.length>0 &&  params.length == values.length;
		if (flag){
			List<NameValuePair> nameValuePairList = new ArrayList<>();
			for(int i =0; i<params.length; i++){
				nameValuePairList.add(new BasicNameValuePair(params[i].toString(),values[i].toString()));
			}
			return nameValuePairList;
		}else{
			log.error("HttpClient errorMsg：{}", "请求参数为空且参数长度不一致");
		}
		return null;
	}

    /**
     * 发送http
     * @param httpRequestBase
     * @return
     */
    public static HttpResult http(HttpRequestBase httpRequestBase){
        /**
         * 设置基础超时
         */
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(HttpConnectionPool.CONNECT_TIMEOUT)
                .setConnectTimeout(HttpConnectionPool.CONNECT_TIMEOUT)
                .setSocketTimeout(HttpConnectionPool.SOCKET_TIMEOUT).build();
            httpRequestBase.setConfig(requestConfig);

        HttpResult httpResult = new HttpResult();

        /*
            String url = httpRequestBase.getURI().toString();
            CloseableHttpClient httpClient = HttpConnectionPool.getHttpClient(url);
         */

        //CloseableHttpClient httpClient = HttpClientPools.getHttpClient();

        CloseableHttpClient httpClient = HttpClients.createDefault();//如果不采用连接池就是这种方式获取连接*/
        try {

            String path = httpRequestBase.getURI().getPath();
            httpResult.setUrl(path);

            CloseableHttpResponse response = httpClient.execute(httpRequestBase, HttpClientContext.create());

            HttpEntity entity = response.getEntity();
            String responseStr = EntityUtils.toString(entity, "UTF-8");
            int status = response.getStatusLine().getStatusCode();
            httpResult.setCode(status);
            httpResult.setMessage(responseStr);

            response.close();

        }catch (Exception e){
            log.error("http send error",e);
        }finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("httpclient-close",e);
            }
        }
        return httpResult;
    }
}

/**
 * 连接池
 */
@Slf4j
class HttpConnectionPool{

    /**
     * 设置连接建立的超时时间为10s
     */
    public static final int CONNECT_TIMEOUT = Config.getHttpConnectTimeout();
    public static final int SOCKET_TIMEOUT = Config.getHttpSocketTimeout();

    /**
     * 最大连接数
     */
    private static final int MAX_CONN = Config.getHttpMaxPoolSize();
    private static final int Max_PRE_ROUTE = Config.getHttpMaxPoolSize();
    private static final int MAX_ROUTE = Config.getHttpMaxPoolSize();

    /**
     * 发送请求的客户端单例
     */
    private static CloseableHttpClient httpClient;

    /**
     * 连接池管理类
     */
    private static PoolingHttpClientConnectionManager manager;
    private static ScheduledExecutorService monitorExecutor;

    /**
     * 相当于线程锁,用于线程安全
     */
    private final static Object syncLock = new Object();


    /**
     * 获取请求客户端
     * @param url
     * @return
     */
    public static CloseableHttpClient getHttpClient(String url){
        String hostName = url.split("/")[2];
        int port = 80;
        if (hostName.contains(":")){
            String[] args = hostName.split(":");
            hostName = args[0];
            port = Integer.parseInt(args[1]);
        }

        if (httpClient == null){
            //多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
            synchronized (syncLock){
                if (httpClient == null){
                    httpClient = createHttpClient(hostName, port);
                    //开启监控线程,对异常和空闲线程进行关闭
                    monitorExecutor = Executors.newScheduledThreadPool(1);
                    monitorExecutor.scheduleAtFixedRate(new TimerTask() {
                        @Override
                        public void run() {
                            //关闭异常连接
                            manager.closeExpiredConnections();
                            //关闭5s空闲的连接
                            manager.closeIdleConnections(Config.getHttpIdelTimeout(), TimeUnit.MILLISECONDS);
//                            logger.info("close expired and idle for over 5s connection");
                        }
                    }, Config.getHttpMonitorInterval(), Config.getHttpMonitorInterval(), TimeUnit.MILLISECONDS);
                }
            }
        }
        return httpClient;
    }

    /**
     * 根据host和port构建httpclient实例
     * @param host 要访问的域名
     * @param port 要访问的端口
     * @return
     */
    public static CloseableHttpClient createHttpClient(String host, int port){
        ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainSocketFactory)
                .register("https", sslSocketFactory).build();

        manager = new PoolingHttpClientConnectionManager(registry);
        //设置连接参数
        // 最大连接数
        manager.setMaxTotal(MAX_CONN);
        // 路由最大连接数
        manager.setDefaultMaxPerRoute(Max_PRE_ROUTE);

        HttpHost httpHost = new HttpHost(host, port);
        manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE);

        //请求失败时,进行请求重试
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3){
                    //重试超过3次,放弃请求
                    log.error("重试超过3次,放弃请求");
                    return false;
                }
                    log.error("retry has " + i);
                if (e instanceof NoHttpResponseException){
                    //服务器没有响应,可能是服务器断开了连接,应该重试
                    log.error("服务器没有响应,可能是服务器断开了连接,应该重试");
                    return true;
                }
                if (e instanceof SSLHandshakeException){
                    // SSL握手异常
                    log.error("SSL握手异常");
                    return false;
                }
                if (e instanceof InterruptedIOException){
                    //超时
                    log.error("超时");
                    return false;
                }
                if (e instanceof UnknownHostException){
                    // 服务器不可达
                    log.error("服务器不可达");
                    return false;
                }
                if (e instanceof ConnectTimeoutException){
                    // 连接超时
                    log.error("连接超时");
                    return false;
                }
                if (e instanceof SSLException){
                    log.error("SSLException");
                    return false;
                }

                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)){
                    //如果请求不是关闭连接的请求
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();
        return client;
    }

    static class Config{
        /**
         * 连接超时时间
         */
        static int httpConnectTimeout = 30000;

        /**
         * socket超时时间
         */
        static int httpSocketTimeout = 20000;

        /**
         * 最大连接池大小
         */
        static int httpMaxPoolSize = 400;

        /**
         * 监控间隔时间
         */
        static int httpMonitorInterval = 3000;

        /**
         * 空闲的连接
         */
        public static int httpIdelTimeout = 2000;

        public static int getHttpIdelTimeout() {
            return httpIdelTimeout;
        }

        public static int getHttpSocketTimeout() {
            return httpSocketTimeout;
        }

        public static int getHttpMaxPoolSize() {
            return httpMaxPoolSize;
        }

        public static int getHttpMonitorInterval() {
            return httpMonitorInterval;
        }

        public static int getHttpConnectTimeout() {
            return httpConnectTimeout;
        }
    }
}


/**
 * 	Http连接池
 */
class HttpClientPools {

	static PoolingHttpClientConnectionManager cm = null;

	/**
	 *  初始化连接池
	 */
	static  {
		LayeredConnectionSocketFactory sslsf = null;
		try {
			sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
				.register("https", sslsf)
				.register("http", new PlainConnectionSocketFactory())
				.build();
		cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		cm.setMaxTotal(300);
		cm.setDefaultMaxPerRoute(50);
	}



}
