package com.kmall.common.utils;

import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @author Scott Chen
 * @date 2017/3/13
 */
public class OkHttpUtils {
    private static final Logger logger = LoggerFactory.getLogger(OkHttpUtils.class);

    public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=UTF-8");
    public static final MediaType MEDIA_TYPE_XML = MediaType.parse("application/xml;charset=UTF-8");
    public static final MediaType MEDIA_TYPE_TEXT_HTML = MediaType.parse("text/html;charset=UTF-8");
    private static final MediaType MEDIA_TYPE_FORM= MediaType.parse("application/x-www-form-urlencoded");

    private static final String SSL = "SSL";


    public static final int CONNECT_TIME_OUT = 20;
    public static final int READ_TIME = 30;
    public static final int WRITE_TIME = 30;

    private static final ThreadLocal<OkHttpClient> okHttpClientThreadLocal = new ThreadLocal<>();
    private static final ThreadLocal<OkHttpClient> okHttpClientSslThreadLocal = new ThreadLocal<>();


    /**
     * 禁止调用无参构造
     */
    private OkHttpUtils() {}

    /**
     * 默认http
     * @return
     */
    private static OkHttpClient okHttpClient(){
        return okHttpClient(null);
    }

    /**
     * https 和 http
     * @param ssl 如果需要SSL认证https, ssl值为SSL, 如果ssl为空, 则为http
     * @return
     */
    private static OkHttpClient okHttpClient(String ssl){
        return StringUtils.isNotBlank(ssl) && SSL.equals(ssl) ? okHttpInstanceSsl() : okHttpInstance();
    }

    /**
     * https 和 http
     * 如果要信息所有证书,
     *      <code>ssl</code>必须为SSL,
     *      <code>ignoreHostnameVerifier</code>为true,
     *      <code>InputStream... certificates</code>为null
     *
     * @param ssl
     * @param ignoreHostnameVerifier 是否忽略Hostname验证
     * @param certificates 证书流
     * @return
     */
    private static OkHttpClient okHttpClient(String ssl, boolean ignoreHostnameVerifier, InputStream... certificates){
        return StringUtils.isNotBlank(ssl) && SSL.equalsIgnoreCase(ssl)
                ? okHttpInstanceSsl(ignoreHostnameVerifier, certificates)
                : okHttpInstance();
    }

    /**
     * OkHttpClient 实例
     * @return
     */
    private static OkHttpClient okHttpInstance() {
        OkHttpClient client = okHttpClientThreadLocal.get();
        if (client == null) {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
                    .readTimeout(READ_TIME, TimeUnit.SECONDS)
                    .writeTimeout(WRITE_TIME, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true);

            client = builder.build();
            okHttpClientThreadLocal.set(client);
        }
        return client;
    }


    /**
     * OkHttpClient ssl 实例
     * 忽略Hostname验证, 并信息所有证书
     * @return
     */
    private static OkHttpClient okHttpInstanceSsl() {
        return okHttpInstanceSsl(true, null);
    }

    /**
     * OkHttpClient ssl 实例
     * 如果要信息所有证书, <code>InputStream... certificates</code>为null
     * @param ignoreHostnameVerifier 是否忽略Hostname验证
     * @param certificates 证书流
     * @return
     */
    private static OkHttpClient okHttpInstanceSsl(boolean ignoreHostnameVerifier, InputStream... certificates) {
        OkHttpClient client = okHttpClientSslThreadLocal.get();
        if (client == null) {

            X509TrustManager[] trustManager = buildX509TrustManager(certificates);
            SSLSocketFactory sslSocketFactory = buildSSLSocketFactory(trustManager);

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
                    .readTimeout(READ_TIME, TimeUnit.SECONDS)
                    .writeTimeout(WRITE_TIME, TimeUnit.SECONDS);

            //是否忽略host验证
            if (ignoreHostnameVerifier) {
                builder.hostnameVerifier(getHostnameVerifier());
            }
            builder.sslSocketFactory(sslSocketFactory, trustManager[0])
                    .retryOnConnectionFailure(true);

            client = builder.build();
            okHttpClientSslThreadLocal.set(client);
        }
        return client;
    }


    //------------------------------ build RequestBody ------------------------------


    public static RequestBody buildRequestBody(MediaType mediaType, String json) {
        return RequestBody.create(mediaType, json);
    }

    public static RequestBody buildRequestBody(String key, Object value) {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        Map<String, Object> param = new HashMap<>();
        param.put(key, value);
        return buildRequestBody(param);
    }

    public static RequestBody buildMapStringRequestBody(Map<String, String> params) {
        FormBody.Builder builder = new FormBody.Builder();
        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            builder.add(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }

    /**
     * 构造RequestBody
     *
     * @param params
     * @return
     */
    public static RequestBody buildRequestBody(Map<String, Object> params) {
        FormBody.Builder builder = new FormBody.Builder();
        Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = iterator.next();
            builder.add(entry.getKey(), entry.getValue()==null?"":JacksonUtils.toJson(entry.getValue()));
        }
        return builder.build();
    }


    //------------------------------ build Request ------------------------------


    /**
     * 以字符串数据构建
     * @param json
     * @param url
     * @param type
     * @return
     */
    public static Request buildRequest(String json, String url, String type) {
        //默认JSON
        MediaType mediaType = MEDIA_TYPE_JSON;

        if (type.equalsIgnoreCase("text_html")) {
            mediaType = MEDIA_TYPE_TEXT_HTML;
        }else if (type.equalsIgnoreCase("form")) {
            mediaType = MEDIA_TYPE_FORM;
        }else if (type.equalsIgnoreCase("xml")) {
            mediaType = MEDIA_TYPE_XML;
        }

        RequestBody body = buildRequestBody(mediaType, json);
        return buildRequest(body, url);
    }


    public static Request buildStringRequest(Map<String, String> param, String url) {
        return buildRequest(buildMapStringRequestBody(param), url);
    }

    /**
     * 构建Request
     * @param param
     * @param url
     * @return
     */
    public static Request buildRequest(Map<String, Object> param, String url) {
        return buildRequest(buildRequestBody(param), url);
    }

    /**
     * 构建Request
     * @param key
     * @param value
     * @param url
     * @return
     */
    public static Request buildRequest(String key, Object value, String url) {
        return buildRequest(buildRequestBody(key, value), url);
    }

    /**
     * 构建Request
     * @param body
     * @param url
     * @return
     */
    public static Request buildRequest(RequestBody body, String url) {
        return new Request.Builder()
                .url(url)
                .post(body)
                .build();
    }


    //------------------------------ post ------------------------------

    public static String post(String json, String url, String type, String ssl) {
        return post(buildRequest(json, url, type), ssl);
    }

    public static String post(String key, Object value, String url, String ssl) {
        return post(buildRequest(key, value, url), ssl);
    }

    public static String postString(Map<String, String> param, String url, String ssl) {
        return post(buildStringRequest(param, url), ssl);
    }

    public static String post(Map<String, Object> param, String url, String ssl) {
        return post(buildRequest(param, url), ssl);
    }


    /**
     * 同步访问,返回结果字符串
     * 可能超时
     *
     * @param request
     * @param ssl
     * @return
     * @throws IOException
     */
    public static String post(Request request, String ssl) {
        String result = "";
        try {
            Response response = okHttpClient(ssl).newCall(request).execute();

            if (response.isSuccessful()) {
                result = response.body().string();
            } else {
                logger.error("okhttp3 post failed.");
            }
        } catch (IOException e) {
            logger.error("okhttp3 post throw IOException, {}", e.getMessage());
        }
        return result;
    }



    /**
     * 同步访问,返回Response
     * 可能超时
     *
     * @param request
     * @param ssl
     * @return
     * @throws IOException
     */
    public static String postReturnResponse(Request request, String ssl) {
        try {
            return okHttpClient(ssl).newCall(request).execute().body().string();
        } catch (IOException e) {
            logger.error("okhttp3 post throw IOException, {}", e.getMessage());
        }
        return null;
    }

    /**
     * 异步访问,回调结果
     * @param request
     * @param ssl
     * @param responseCallback
     */
    public static void asyncPostCallback(Request request, String ssl,  Callback responseCallback) {
        okHttpClient(ssl).newCall(request).enqueue(responseCallback);
    }

    /**
     * 异步访问,无结果返回
     * @param request
     * @param ssl
     */
    public static void asyncPost(Request request, String ssl) {
        okHttpClient(ssl).newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });
    }


    //------------------------------ ssl ------------------------------

    /**
     * 获取主机验证 HostnameVerifier
     * @return
     */
    public static HostnameVerifier getHostnameVerifier() {
        return new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        };
    }


    /**
     * 自定义证书
     *
     * @param certificates
     * @return
     */
    private static KeyStore generateKeyStore(InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);

            int index = 0;
            if (certificates !=null && certificates.length > 0) {
                for (InputStream certificate : certificates) {
                    String certificateAlias = Integer.toString(index++);
                    keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

                    try {
                        if (certificate != null) {
                            certificate.close();
                        }
                    } catch (IOException e) {
                    }
                }
            }

            return keyStore;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 证书管理
     * 信任所有证书
     * @return
     */
    private static X509TrustManager[] buildX509TrustManager(){
        return buildX509TrustManager(null);
    }

    /**
     * 证书管理
     * 如果要信任所有证书，<code>InputStream... certificates</code>为null
     * @param certificates 证书文件流
     * @return
     */
    private static X509TrustManager[] buildX509TrustManager(InputStream... certificates) {
        try {
            if (certificates == null) {
                // 信任所有证书
                return new X509TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                        }

                        @Override
                        public X509Certificate[] getAcceptedIssuers() {
                            return new X509Certificate[]{};
                        }
                    }
                };
            }

            // 信任自定义证书
            KeyStore keyStore = generateKeyStore(certificates);

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());

            trustManagerFactory.init(keyStore);

            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }

            return (X509TrustManager[]) trustManagers;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



    private static SSLSocketFactory buildSSLSocketFactory() {
        return buildSSLSocketFactory((InputStream)null);
    }

    private static SSLSocketFactory buildSSLSocketFactory(InputStream... certificates) {
        try {
            TrustManager[] trustManager = buildX509TrustManager(certificates);
            return buildSSLSocketFactory(trustManager);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static SSLSocketFactory buildSSLSocketFactory(TrustManager[] trustManager) {
        try {
            if (trustManager == null) {
                throw new IllegalStateException("TrustManager is null");
            }

            SSLContext sslContext = SSLContext.getInstance(SSL);
            sslContext.init(null, trustManager, new SecureRandom());

            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }




}
