/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.http;

import java.io.IOException;
import java.security.KeyStore;
import java.util.Base64;
import java.util.List;

import javax.net.ssl.SSLContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
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.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
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.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
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.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

import com.crunii.micro.common.http.handler.HttpReqErrorHandler;
import com.crunii.micro.common.http.interceptor.LogHttpReqInterceptor;
import com.crunii.micro.common.http.interceptor.LogHttpRspInterceptor;

/**
 * @author 田平 create 2016年1月26日上午11:33:51 支持https的自签证书和单独的url设置超时参数
 */
public class HttpClientUtil {
	private static final Log log = LogFactory.getLog(HttpClientUtil.class);
	private static final String defaultEncode = "UTF-8";// 默认编码
	private static final int socketTimeout = 60000;// 默认60秒 响应超时
	private static final int requestTimeout = 30000;// 默认30秒 请求超时
	private static final int connectTimeout = 30000;// 默认30秒 链接超时

	private static final int maxTotal = 512;// 默认最大连接数 默认
	private static final int defaultMaxPerRoute = 64;// 默认每个路由基础的连接
	private static RequestConfig defaultReqConfig;
	private static PoolingHttpClientConnectionManager cm;
	private static CloseableHttpClient httpClient;
	static {
		RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory> create();
		ConnectionSocketFactory plainSF = new PlainConnectionSocketFactory();
		registryBuilder.register("http", plainSF);
		// 指定信任密钥存储对象和连接套接字工厂
		try {
			KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
			SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustAllStrategy()).build();
			LayeredConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
			registryBuilder.register("https", sslSF);
			Registry<ConnectionSocketFactory> registry = registryBuilder.build();
			defaultReqConfig = RequestConfig.custom().setConnectionRequestTimeout(requestTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
			cm = new PoolingHttpClientConnectionManager(registry);
			cm.setMaxTotal(maxTotal);
			cm.setDefaultMaxPerRoute(defaultMaxPerRoute);
			httpClient = HttpClients.custom()
					.setRetryHandler(new HttpReqErrorHandler())
					.addInterceptorFirst(new LogHttpReqInterceptor())
					.addInterceptorFirst(new LogHttpRspInterceptor())
					.setConnectionManager(cm).build();
		} catch (Exception e) {
			throw new RuntimeException("httpsClient build failure!", e);
		}
	}

	public static void releaseResources(HttpReqRsp reqRsp) {
		if (reqRsp != null) {
			releaseResources(reqRsp.getReq(), reqRsp.getRsp());
		}
	}

	public static void releaseResources(HttpRequestBase req, CloseableHttpResponse rsp) {
		releaseReq(req);
		releaseRsp(rsp);
	}

	public static void releaseReq(HttpRequestBase req) {
		if (req != null) {
			req.releaseConnection();
		}
	}

	public static void releaseRsp(CloseableHttpResponse rsp) {
		if (rsp != null) {
			try {
				rsp.close();
			} catch (IOException e) {
				log.warn("reqRsp.getRsp().close() error!", e);
			}
		}
	}

	/**
	 * <pre>
	 * &#64;注意自己释放请求和响应的资源@
	 * public static void releaseReq(HttpRequestBase req)
	 * public static void releaseRsp(CloseableHttpResponse rsp)
	 * </pre>
	 *
	 * @return
	 */
	public static CloseableHttpClient getClient() {
		return httpClient;
	}

	/**
	 * 获取basic auth的header
	 *
	 * @param username
	 * @param password
	 * @return
	 */
	public static Header getBasicAuth(String username, String password) {
		String auth = username + ":" + password;
		String authHeader = "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
		Header header = new BasicHeader("Authorization", authHeader);
		return header;
	}

	/**
	 * 单位为毫秒
	 *
	 * @param reqTimeout 请求超时
	 * @param connTimeout 连接超时
	 * @param sockTimeout 响应超时
	 * @return
	 */
	public static RequestConfig getConf(int reqTimeout, int connTimeout, int sockTimeout) {
		return RequestConfig.custom().setConnectionRequestTimeout(reqTimeout).setConnectTimeout(connTimeout).setSocketTimeout(sockTimeout).build();
	}

	/**
	 * 单位为毫秒
	 *
	 * @param reqTimeout
	 * @param connTimeout
	 * @return
	 */
	public static RequestConfig getConf(int reqTimeout, int connTimeout) {
		return RequestConfig.custom().setConnectionRequestTimeout(reqTimeout).setConnectTimeout(connTimeout).setSocketTimeout(socketTimeout).build();
	}

	/**
	 * 单位是毫秒 响应超时
	 *
	 * @param socketTimeout
	 * @return
	 */
	public static RequestConfig getSockConf(int socketTimeout) {
		return RequestConfig.custom().setConnectionRequestTimeout(requestTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
	}

	/**
	 * 单位是毫秒 请求超时
	 *
	 * @param reqTimeout
	 * @return
	 */
	public static RequestConfig getReqConf(int reqTimeout) {
		return RequestConfig.custom().setConnectionRequestTimeout(reqTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
	}

	/**
	 * 单位是毫秒 连接超时
	 *
	 * @param connTimeout
	 * @return
	 */
	public static RequestConfig getConnConf(int connTimeout) {
		return RequestConfig.custom().setConnectionRequestTimeout(requestTimeout).setConnectTimeout(connTimeout).setSocketTimeout(socketTimeout).build();
	}

	/**
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @return
	 */
	public static String requestString(String url, List<NameValuePair> params, String methodName) {
		return requestString(url, params, methodName, null, null, null);
	}

	/**
	 * @param url
	 * @param params
	 * @param methodName
	 * @param config public RequestConfig getReqConf(int reqTimeout) 通过这个可以为每个url设置独立的超时时间
	 * @return
	 */
	public static String requestString(String url, List<NameValuePair> params, String methodName, RequestConfig config) {
		return requestString(url, params, methodName, null, null, config);
	}

	public static String requestString(String url, List<NameValuePair> params, String methodName, List<Header> headers) {
		return requestString(url, params, methodName, headers, null, null);
	}

	public static String requestString(String url, List<NameValuePair> params, String methodName, List<Header> headers, RequestConfig config) {
		return requestString(url, params, methodName, headers, null, config);
	}

	public static String requestString(String url, List<NameValuePair> params, String methodName, List<Header> headers, String urlEncode, RequestConfig config) {
		return requestString(url, params, methodName, headers, urlEncode, null, config);
	}

	public static String requestString(String url, List<NameValuePair> params, String methodName, List<Header> headers, String urlEncode, String entityEncode, RequestConfig config) {
		HttpReqRsp reqRsp = request(url, params, methodName, headers, urlEncode, config);
		try {
			int status = reqRsp.getRsp().getStatusLine().getStatusCode();
			if (status >= 200 && status < 300) {
				try {
					String encode = defaultEncode;
					if (StringUtils.hasText(entityEncode)) {
						encode = entityEncode;
					}
					return EntityUtils.toString(reqRsp.getRsp().getEntity(), encode);
				} catch (Exception e) {
					throw new RuntimeException(String.format("EntityUtils.toString error!%s", reqRsp.getRsp().toString()), e);
				}
			} else {
				throw new RuntimeException("request Unexpected response status: " + status);
			}
		} finally {
			releaseResources(reqRsp);
		}

	}

	/**
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @return
	 */
	public static byte[] requestBA(String url, List<NameValuePair> params, String methodName) {
		return requestBA(url, params, methodName, null, null, null);
	}

	/**
	 * @param url
	 * @param params
	 * @param methodName
	 * @param config public RequestConfig getReqConf(int reqTimeout) 通过这个可以为每个url设置独立的超时时间
	 * @return
	 */
	public static byte[] requestBA(String url, List<NameValuePair> params, String methodName, RequestConfig config) {
		return requestBA(url, params, methodName, null, null, config);
	}

	public static byte[] requestBA(String url, List<NameValuePair> params, String methodName, List<Header> headers) {
		return requestBA(url, params, methodName, headers, null, null);
	}

	public static byte[] requestBA(String url, List<NameValuePair> params, String methodName, List<Header> headers, RequestConfig config) {
		return requestBA(url, params, methodName, headers, null, config);
	}

	public static byte[] requestBA(String url, List<NameValuePair> params, String methodName, List<Header> headers, String urlEncode, RequestConfig config) {
		HttpReqRsp reqRsp = request(url, params, methodName, headers, urlEncode, config);
		try {
			int status = reqRsp.getRsp().getStatusLine().getStatusCode();
			if (status >= 200 && status < 300) {
				try {
					return EntityUtils.toByteArray(reqRsp.getRsp().getEntity());
				} catch (Exception e) {
					throw new RuntimeException(String.format("EntityUtils.toByteArray error!%s", reqRsp.getRsp().toString()), e);
				}
			} else {
				throw new RuntimeException("request Unexpected response status: " + status);
			}
		} finally {
			releaseResources(reqRsp);
		}
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @return
	 */
	public static HttpReqRsp request(String url, List<NameValuePair> params, String methodName) {
		return request(url, params, methodName, null, null, null);
	}

	/**
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @param config public RequestConfig getReqConf(int reqTimeout) 通过这个可以为每个url设置独立的超时时间
	 * @return
	 */
	public static HttpReqRsp request(String url, List<NameValuePair> params, String methodName, RequestConfig config) {
		return request(url, params, methodName, null, null, config);
	}

	/**
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @param headers
	 * @return
	 */
	public static HttpReqRsp request(String url, List<NameValuePair> params, String methodName, List<Header> headers) {
		return request(url, params, methodName, headers, null, null);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace 不区分大小写
	 * @param headers
	 * @param config
	 * @return
	 */
	public static HttpReqRsp request(String url, List<NameValuePair> params, String methodName, List<Header> headers, RequestConfig config) {
		return request(url, params, methodName, headers, null, config);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param params
	 * @param methodName 支持put post get delete head patch options trace
	 * @param headers
	 * @param urlEncode 默认utf-8
	 * @param config 可以设置超时时间等
	 * @return
	 */
	public static HttpReqRsp request(String url, List<NameValuePair> params, String methodName, List<Header> headers, String urlEncode, RequestConfig config) {
		CloseableHttpResponse rsp = null;
		HttpRequestBase req = null;
		String encode = defaultEncode;
		if (StringUtils.hasText(urlEncode)) {
			encode = urlEncode;
		}
		try {
			String realUrl = url;
			if (params != null && params.size() > 0) {
				realUrl = url + "?" + EntityUtils.toString(new UrlEncodedFormEntity(params, encode));
			}
			if (methodName.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
				req = new HttpGet(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpPost.METHOD_NAME)) {
				req = new HttpPost(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpPut.METHOD_NAME)) {
				req = new HttpPut(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpDelete.METHOD_NAME)) {
				req = new HttpDelete(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
				req = new HttpHead(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpPatch.METHOD_NAME)) {
				req = new HttpPatch(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpOptions.METHOD_NAME)) {
				req = new HttpOptions(realUrl);
			} else if (methodName.equalsIgnoreCase(HttpTrace.METHOD_NAME)) {
				req = new HttpTrace(realUrl);
			} else {
				throw new RuntimeException("unsupport the methodName " + methodName);
			}

			if (config != null) {
				req.setConfig(config);
			} else {
				req.setConfig(defaultReqConfig);
			}
			if (headers != null && headers.size() > 0) {
				for (Header header : headers) {
					req.addHeader(header);
				}
			}
			rsp = httpClient.execute(req);
			return new HttpReqRsp(req, rsp);
		} catch (Exception e) {
			releaseResources(req, rsp);
			throw new RuntimeException(e);
		}
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param entity
	 * @param config config 可以设置超时时间等
	 * @return 默认post方式
	 */
	public static HttpReqRsp requestByEntity(String url, HttpEntity entity) {
		return requestByEntity(url, entity, "POST", null, null);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param entity
	 * @param config config 可以设置超时时间等
	 * @return 默认post方式
	 */
	public static HttpReqRsp requestByEntity(String url, HttpEntity entity, RequestConfig config) {
		return requestByEntity(url, entity, "POST", null, config);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param entity
	 * @param headers
	 * @return 默认post方式
	 */
	public static HttpReqRsp requestByEntity(String url, HttpEntity entity, List<Header> headers) {
		return requestByEntity(url, entity, "POST", headers, null);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param entity
	 * @param headers
	 * @param config 可以设置超时时间等
	 * @return 默认post方式
	 */
	public static HttpReqRsp requestByEntity(String url, HttpEntity entity, List<Header> headers, RequestConfig config) {
		return requestByEntity(url, entity, "POST", headers, config);
	}

	/**
	 * @注意自己释放请求和响应的资源@ public static void releaseResources(HttpReqRsp reqRsp)
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param headers
	 * @param config 可以设置超时时间等
	 * @return
	 */
	public static HttpReqRsp requestByEntity(String url, HttpEntity entity, String methodName, List<Header> headers, RequestConfig config) {
		CloseableHttpResponse rsp = null;
		HttpRequestBase req = null;
		try {
			if (methodName.equalsIgnoreCase(HttpPost.METHOD_NAME)) {
				HttpPost httpPost = new HttpPost(url);
				httpPost.setEntity(entity);
				req = httpPost;
			} else if (methodName.equalsIgnoreCase(HttpPut.METHOD_NAME)) {
				HttpPut httpPut = new HttpPut(url);
				httpPut.setEntity(entity);
				req = httpPut;
			} else if (methodName.equalsIgnoreCase(HttpPatch.METHOD_NAME)) {
				HttpPatch httpPatch = new HttpPatch(url);
				httpPatch.setEntity(entity);
				req = httpPatch;
			} else {
				throw new RuntimeException("unsupport the methodName " + methodName);
			}
			if (config != null) {
				req.setConfig(config);
			} else {
				req.setConfig(defaultReqConfig);
			}
			if (headers != null && headers.size() > 0) {
				for (Header header : headers) {
					req.addHeader(header);
				}
			}
			rsp = httpClient.execute(req);
			return new HttpReqRsp(req, rsp);
		} catch (Exception e) {
			releaseResources(req, rsp);
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @return
	 */
	public static String reqStrByEntity(String url, HttpEntity entity, String methodName) {
		return reqStrByEntity(url, entity, methodName, null, null);
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param config
	 * @return
	 */
	public static String reqStrByEntity(String url, HttpEntity entity, String methodName, RequestConfig config) {
		return reqStrByEntity(url, entity, methodName, null, null, config);
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param headers
	 * @return
	 */
	public static String reqStrByEntity(String url, HttpEntity entity, String methodName, List<Header> headers) {
		return reqStrByEntity(url, entity, methodName, headers, null, null);
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param headers
	 * @param config
	 * @return
	 */
	public static String reqStrByEntity(String url, HttpEntity entity, String methodName, List<Header> headers, RequestConfig config) {
		return reqStrByEntity(url, entity, methodName, headers, null, config);
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param headers
	 * @param entityEncode
	 * @param config
	 * @return
	 */
	public static String reqStrByEntity(String url, HttpEntity entity, String methodName, List<Header> headers, String entityEncode, RequestConfig config) {
		HttpReqRsp reqRsp = requestByEntity(url, entity, methodName, headers, config);
		try {
			int status = reqRsp.getRsp().getStatusLine().getStatusCode();
			if (status >= 200 && status < 300) {
				try {
					String encode = defaultEncode;
					if (StringUtils.hasText(entityEncode)) {
						encode = entityEncode;
					}
					return EntityUtils.toString(reqRsp.getRsp().getEntity(), encode);
				} catch (Exception e) {
					throw new RuntimeException(String.format("EntityUtils.toString error!%s", reqRsp.getRsp().toString()), e);
				}
			} else {
				throw new RuntimeException("request Unexpected response status: " + status);
			}
		} finally {
			releaseResources(reqRsp);
		}

	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @return
	 */
	public static byte[] reqBAByEntity(String url, HttpEntity entity, String methodName) {
		return reqBAByEntity(url, entity, methodName, null, null);
	}

	public static byte[] reqBAByEntity(String url, HttpEntity entity, String methodName, List<Header> headers) {
		return reqBAByEntity(url, entity, methodName, headers, null);
	}

	public static byte[] reqBAByEntity(String url, HttpEntity entity, String methodName, RequestConfig config) {
		return reqBAByEntity(url, entity, methodName, null, config);
	}

	/**
	 * @param url
	 * @param entity
	 * @param methodName 支持post put patch
	 * @param headers
	 * @param entityEncode
	 * @param config
	 * @return
	 */
	public static byte[] reqBAByEntity(String url, HttpEntity entity, String methodName, List<Header> headers, RequestConfig config) {
		HttpReqRsp reqRsp = requestByEntity(url, entity, methodName, headers, config);
		try {
			int status = reqRsp.getRsp().getStatusLine().getStatusCode();
			if (status >= 200 && status < 300) {
				try {
					return EntityUtils.toByteArray(reqRsp.getRsp().getEntity());
				} catch (Exception e) {
					throw new RuntimeException(String.format("EntityUtils.toByteArray error!%s", reqRsp.getRsp().toString()), e);
				}
			} else {
				throw new RuntimeException("request Unexpected response status: " + status);
			}
		} finally {
			releaseResources(reqRsp);
		}
	}
}
