package com.house365.commons.system;

import com.google.common.base.Strings;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectStrategy;
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.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.*;
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.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * HttpClient请求工具类
 *
 * @author zhaodeshan
 * @version [版本号, 2015年2月2日]
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class HttpClientUtil {
    public static final int SOCKET_TIMEOUT = 180000;
    public static final int CONNECT_TIMEOUT = 10000;
    public static final int CONNECTION_REQUEST_TIMEOUT = 60000;
    private static final Logger LOG = LoggerFactory.getLogger("InterfaceLog");
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static CloseableHttpClient httpClient = null;

    private static PoolingHttpClientConnectionManager connManager = null;

    static {
        try {
            String proxyURL = null;
            String proxyPort = null;

            SSLContext sslContext = SSLContexts.custom().useTLS().build();
            sslContext.init(null, new TrustManager[] {new X509TrustManager() {

                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(
                    X509Certificate[] certs, String authType
                ) {
                }

                public void checkServerTrusted(
                    X509Certificate[] certs, String authType
                ) {
                }
            }}, null);

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register(
                "http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(
                sslContext)).build();

            connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

            HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

                public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                    if (executionCount >= 5) {
                        LOG.warn("=====httpClient has retry 5 times====");
                        // 如果已经重试了5次，就放弃
                        return false;
                    }
                    if (exception instanceof InterruptedIOException) {
                        // 超时
                        LOG.warn("=====httpClient InterruptedIOException====");
                        return false;
                    }
                    if (exception instanceof UnknownHostException) {
                        // 目标服务器不可达
                        LOG.warn("=====httpClient UnknownHostException====");
                        return false;
                    }
                    if (exception instanceof SSLException) {
                        // ssl握手异常
                        LOG.warn("=====httpClient SSLException====");
                        return false;
                    }
                    HttpClientContext clientContext = HttpClientContext.adapt(context);
                    HttpRequest request = clientContext.getRequest();
                    boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                    if (idempotent) {
                        // 如果请求是幂等的，就再次尝试
                        if (LOG.isInfoEnabled()) {
                            LOG.info("请求 [" + request.getRequestLine() + "] 是幂等的，再次尝试");
                        }
                        return true;
                    }
                    return false;
                }

            };

            ConnectionKeepAliveStrategy keepAliveStrategy = new ConnectionKeepAliveStrategy() {
                public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                    // Honor 'keep-alive' header
                    HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                    while (it.hasNext()) {
                        HeaderElement he = it.nextElement();
                        String param = he.getName();
                        String value = he.getValue();
                        if (value != null && param.equalsIgnoreCase("timeout")) {
                            try {
                                return Long.parseLong(value) * 1000;
                            } catch (NumberFormatException ignore) {
                            }
                        }
                    }
                    // otherwise keep alive for 60 seconds
                    return 60 * 1000;
                }

            };

            RedirectStrategy redirectStrategy = new MyRedirectStrategy();

            // Create socket configuration
            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
            connManager.setDefaultSocketConfig(socketConfig);
            // Create message constraints
            MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(
                2000).build();
            // Create connection configuration
            ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(
                CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(
                Consts.UTF_8).setMessageConstraints(messageConstraints).build();
            connManager.setDefaultConnectionConfig(connectionConfig);
            connManager.setMaxTotal(200);
            connManager.setDefaultMaxPerRoute(200);

            if (!Strings.isNullOrEmpty(proxyURL) && !Strings.isNullOrEmpty(proxyPort)) {
                HttpHost proxy = new HttpHost(proxyURL, Integer.parseInt(proxyPort));
                DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
                //                httpClient = HttpClients.custom().setRoutePlanner(routePlanner).build();
                httpClient = HttpClients.custom().setConnectionManager(connManager).setRetryHandler(
                    retryHandler).setKeepAliveStrategy(keepAliveStrategy).setRedirectStrategy(
                    redirectStrategy).setRoutePlanner(routePlanner).build();
            } else {
                httpClient = HttpClients.custom().setConnectionManager(connManager).setRetryHandler(
                    retryHandler).setKeepAliveStrategy(keepAliveStrategy).setRedirectStrategy(redirectStrategy).build();
            }
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        }
    }


    /**
     * 执行Http Get请求获取JSON字符串数据
     *
     * @param url     请求地址
     * @param params  请求参数
     * @param charSet 字符编码
     * @return String JSON字符串
     * @throws IOException
     * @author zhaodeshan
     * @version [版本号, 2015年2月2日]
     * @since [产品/模块版本](可选)
     */
    public static String doGet(String url, List<NameValuePair> params, String charSet) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse httpResponse = null;
        long beginTime = System.currentTimeMillis();
        try {
            HttpGet httpGet = new HttpGet();
            URIBuilder builder = new URIBuilder(url);
            builder.setParameters(params);
            httpGet.setURI(builder.build());

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(
                20000).build();//设置请求和传输超时时间
            httpGet.setConfig(requestConfig);

            httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();

            String body = null;
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK && httpEntity != null) {
                if (StringUtils.isNotBlank(charSet)) {
                    body = EntityUtils.toString(httpEntity, charSet);
                } else {
                    body = EntityUtils.toString(httpEntity, DEFAULT_ENCODING);
                }
                EntityUtils.consume(httpEntity);
            }
            logUrlInfo(beginTime, url);
            return body;
        } catch (Exception e) {
            LOG.error(url + e.toString(), e);
        } finally {
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }

            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }
        }
        return null;
    }

    /**
     * 执行Http Post请求获取JSON字符串数据
     *
     * @param url
     * @param params
     * @param charSet
     * @return String
     * @throws IOException
     * @author zhaodeshan
     * @version [版本号, 2015年5月5日]
     * @since [产品/模块版本](可选)
     */
    public static String doPost(String url, List<NameValuePair> params, String charSet) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse httpResponse = null;
        long beginTime = System.currentTimeMillis();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            String body = null;
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK && httpEntity != null) {
                if (StringUtils.isNotBlank(charSet)) {
                    body = EntityUtils.toString(httpEntity, charSet);
                } else {
                    body = EntityUtils.toString(httpEntity, DEFAULT_ENCODING);
                }
                EntityUtils.consume(httpEntity);
            }

            logUrlInfo(beginTime, url);

            return body;
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        } finally {
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }

            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }
        }
        return null;
    }

    /**
     *  发送application/json 格式数据
     * @param url
     * @param jsonParams
     * @param charSet
     * @return
     */
    public static String doPostJson(String url,String jsonParams, String charSet) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse httpResponse = null;
        long beginTime = System.currentTimeMillis();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-Type", "application/json");
            httpPost.setEntity(new StringEntity(jsonParams, "UTF-8"));
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            String body = null;
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK && httpEntity != null) {
                if (StringUtils.isNotBlank(charSet)) {
                    body = EntityUtils.toString(httpEntity, charSet);
                } else {
                    body = EntityUtils.toString(httpEntity, DEFAULT_ENCODING);
                }
                EntityUtils.consume(httpEntity);
            }

            logUrlInfo(beginTime, url);

            return body;
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        } finally {
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }

            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }
        }
        return null;
    }


    public static String doPostChatset(
        String url, List<NameValuePair> params, String postCharset, String responseCharset
    ) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse httpResponse = null;

        long beginTime = System.currentTimeMillis();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new UrlEncodedFormEntity(params, postCharset));
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            String body = null;
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK && httpEntity != null) {
                if (StringUtils.isNotBlank(responseCharset)) {
                    body = EntityUtils.toString(httpEntity, responseCharset);
                } else {
                    body = EntityUtils.toString(httpEntity, DEFAULT_ENCODING);
                }
                EntityUtils.consume(httpEntity);
            }
            if (LOG.isInfoEnabled()) {
                logUrlInfo(beginTime, url);
            }
            return body;
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        } finally {
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }

            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }
        }
        return null;
    }

    /**
     * 上传文件
     *
     * @param url
     * @param filePath
     * @param charSet
     * @return String
     * @author zhaodeshan
     * @version [版本号, 2015年5月5日]
     * @since [产品/模块版本](可选)
     */
    public static String uploadFile(String url, String filePath, String charSet) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            HttpPost httppost = new HttpPost(url);
            FileBody file = new FileBody(new File(filePath));
            HttpEntity reqEntity = MultipartEntityBuilder.create().addPart("fileData", file).build();
            httppost.setEntity(reqEntity);

            CloseableHttpResponse response = null;
            try {
                response = httpclient.execute(httppost);
                HttpEntity resEntity = response.getEntity();
                String body = null;
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && resEntity != null) {
                    if (StringUtils.isNotBlank(charSet)) {
                        body = EntityUtils.toString(resEntity, charSet);
                    } else {
                        body = EntityUtils.toString(resEntity, DEFAULT_ENCODING);
                    }
                    EntityUtils.consume(resEntity);
                }
                return body;
            } catch (Exception e) {
                LOG.error(e.toString(), e);
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (IOException e) {
                        LOG.error(e.toString(), e);
                    }
                }
            }
        } finally {
            if (httpclient != null) {
                try {
                    httpclient.close();
                } catch (IOException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
        }

        return null;
    }

    /**
     * 重载 doPost
     */
    public static String doPost(String url, List<NameValuePair> params) throws IOException {
        return doPost(url, params, null);
    }

    public static String doPostWithCharset(
        String url, List<NameValuePair> params, String charset, String responseCharset
    ) throws IOException {
        return doPostChatset(url, params, charset, responseCharset);
    }

    /**
     * 重载 doGet
     */
    public static String doGet(String url, List<NameValuePair> params) throws IOException {
        return doGet(url, params, null);
    }

    //超时处理
    public static String doGet(
        String url, List<NameValuePair> params, int timeOut, String charSet
    ) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse httpResponse = null;
        long beginTime = System.currentTimeMillis();
        try {
            HttpGet httpGet = new HttpGet();
            URIBuilder builder = new URIBuilder(url);
            builder.setParameters(params);
            httpGet.setURI(builder.build());

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeOut).setConnectTimeout(
                timeOut).build();//设置请求和传输超时时间
            httpGet.setConfig(requestConfig);

            httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();

            String body = null;
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK && httpEntity != null) {
                if (StringUtils.isNotBlank(charSet)) {
                    body = EntityUtils.toString(httpEntity, charSet);
                } else {
                    body = EntityUtils.toString(httpEntity, DEFAULT_ENCODING);
                }
                EntityUtils.consume(httpEntity);
            }
            if (LOG.isInfoEnabled()) {
                logUrlInfo(beginTime, url);
            }
            return body;
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        } finally {
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }

            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    LOG.error(e.toString(), e);
                }
            }
        }
        return null;
    }

    /**
     * 获取接口JSON字符串
     *
     * @author
     * @version [v1.0.0]
     */
    public static String getJsonData(String urlString) throws Exception {
        /**
         * 获取接口JSON字符串
         */
        String jsonStr = "";
        long beginTime = System.currentTimeMillis();
        URL url = null;
        try {
            url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setRequestMethod("GET");
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                jsonStr += line;
            }
            in.close();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        if (LOG.isInfoEnabled()) {
            logUrlInfo(beginTime, urlString);
        }
        return jsonStr;
    }

    private static void logUrlInfo(long beginTime, String url) {
        if (System.currentTimeMillis() - beginTime > 1000) {
            LOG.info("call url [{}] , use [{}] ms", url, System.currentTimeMillis() - beginTime);
        }
    }

    // JSON转换为Map
    public static Map<String, Object> parseJSON2Map(String jsonStr) {
        Map<String, Object> map = new HashMap<String, Object>();
        // 最外层解析
        JSONObject json = JSONObject.fromObject(jsonStr);
        for (Object k : json.keySet()) {
            Object v = json.get(k);
            // 如果内层还是数组的话，继续解析
            if (v instanceof JSONArray) {
                List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
                @SuppressWarnings("unchecked") Iterator<JSONObject> it = ((JSONArray) v).iterator();
                while (it.hasNext()) {
                    JSONObject json2 = it.next();
                    list.add(parseJSON2Map(json2.toString()));
                }
                map.put(k.toString(), list);
            } else {
                map.put(k.toString(), v);
            }
        }
        return map;
    }

    /**
     * 安全的消耗（获取）响应内容实体
     * <p/>
     * 使用 {@link EntityUtils} 将响应内容实体转换为字符串，同时关闭输入流
     * <p/>
     * //TODO 响应内容太长不适宜使用 EntityUtils
     *
     * @param response        HttpResponse
     * @param responseCharset 响应内容字符集
     * @return 响应内容实体
     * @throws IOException IOException
     */
    private static String consumeResponseEntity(HttpResponse response, String responseCharset) throws IOException {
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            HttpEntity responseEntity = response.getEntity();
            String responseBody = EntityUtils.toString(responseEntity, responseCharset);
            return responseBody;
        } else {
            return null;
        }
    }

    public static String get(String url, String responseCharset) {
        HttpGet getMethod = null;
        try {
            getMethod = new HttpGet(url);
            HttpResponse response = httpClient.execute(getMethod);
            return consumeResponseEntity(response, responseCharset);
        } catch (Exception e) {
            LOG.error("httpclient get error: url " + url, e);
        } finally {
            if (getMethod != null) {
                getMethod.releaseConnection();
            }
        }
        return null;
    }

    /**
     * HTTP Post
     *
     * @param url             请求URL
     * @param paramList       请求参数集合
     * @param paramEncoding   请求参数编码
     * @param responseCharset 响应内容字符集
     * @return 响应内容实体
     */
    public static String post(
        String url, List<NameValuePair> paramList, String paramEncoding, String responseCharset
    ) throws IOException {
        HttpPost post = null;
        try {
            post = new HttpPost(url);
            if (paramList != null) {
                UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(paramList, paramEncoding);
                post.setEntity(formEntity);
            }

            //设置请求和传输超时时间
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(
                CONNECT_TIMEOUT).setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT).build();
            post.setConfig(requestConfig);

            //执行请求
            HttpResponse response = httpClient.execute(post);
            return consumeResponseEntity(response, responseCharset);
        } catch (Exception e) {
            throw e;
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
    }

}
