package com.ff.base.utils.http;

import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * HTTP/HTTPS 请求封装: GET / POST
 * 默认失败重试3次
 * @author admin
 */
public class HttpClientSslUtils {

    /**
     * 默认的字符编码格式
     */
    private static final String DEFAULT_CHAR_SET = "UTF-8";
    /**
     * 默认连接超时时间 (毫秒)
     */
    private static final Integer DEFAULT_CONNECTION_TIME_OUT = 2000;
    /**
     * 默认socket超时时间 (毫秒)
     */
    private static final Integer DEFAULT_SOCKET_TIME_OUT = 3000;

    /** socketTimeOut上限 */
    private static final Integer SOCKET_TIME_OUT_UPPER_LIMIT = 10000;

    /** socketTimeOut下限 */
    private static final Integer SOCKET_TIME_OUT_LOWER_LIMIT = 1000;

    private static CloseableHttpClient getHttpClient() {
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(DEFAULT_SOCKET_TIME_OUT)
                .setConnectTimeout(DEFAULT_CONNECTION_TIME_OUT).build();
        return HttpClients.custom().setDefaultRequestConfig(requestConfig)
                .setRetryHandler(new DefaultHttpRequestRetryHandler()).build();
    }

    private static CloseableHttpClient getHttpClient(Integer socketTimeOut) {
        RequestConfig requestConfig =
                RequestConfig.custom().setSocketTimeout(socketTimeOut).setConnectTimeout(DEFAULT_CONNECTION_TIME_OUT)
                        .build();
        return HttpClients.custom().setDefaultRequestConfig(requestConfig)
                .setRetryHandler(new DefaultHttpRequestRetryHandler()).build();
    }

    private static class TrustAllCerts implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

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

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

    private static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory ssfFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null,  new TrustManager[] { new TrustAllCerts() }, new SecureRandom());

            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }

        return ssfFactory;
    }

    /**
     * http post
     *
     * @param url
     * @param params
     * @return
     * @throws IOException
     */
    public static String doPost(String url, Map<String,Object> params) throws IOException {

        OkHttpClient httpClient = new OkHttpClient.Builder()
                .connectTimeout(10L, TimeUnit.SECONDS)
                .readTimeout(10L, TimeUnit.SECONDS)
                .sslSocketFactory(createSSLSocketFactory(), new TrustAllX509TrustManager())
                .hostnameVerifier((hostname, session) -> true) // 信任所有主机名
                .build();

        //http post 参数
        FormBody.Builder builder =  new FormBody.Builder();
        String key;
        for(Iterator<String> it = params.keySet().iterator(); it.hasNext();) {
            key = it.next();
            builder.add(key, params.get(key).toString());
        }

        Request request = new Request.Builder()
                .url(url)
                .method("POST", builder.build())
                .build();
        return  httpClient.newCall(request).execute().body().string();
    }

    private static class TrustAllX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[0];
        }
    }

    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    public static String doPost(String url, String requestBody) throws Exception {
        return doPost(url, requestBody, ContentType.APPLICATION_JSON);
    }

    public static String doPost(String url, String requestBody, Integer socketTimeOut) throws Exception {
        return doPost(url, requestBody, ContentType.APPLICATION_JSON, null, socketTimeOut);
    }

    public static String doPost(String url, String requestBody, ContentType contentType) throws Exception {
        return doPost(url, requestBody, contentType, null);
    }

    public static String doPost(String url, String requestBody, List<BasicHeader> headers) throws Exception {
        return doPost(url, requestBody, ContentType.APPLICATION_JSON, headers);
    }

    public static String doPost(String url, String requestBody, ContentType contentType, List<BasicHeader> headers)
            throws Exception {
        return doPost(url, requestBody, contentType, headers, getHttpClient());
    }

    public static String doPost(String url, String requestBody, ContentType contentType, List<BasicHeader> headers,
                                Integer socketTimeOut) throws Exception {
        if (socketTimeOut < SOCKET_TIME_OUT_LOWER_LIMIT || socketTimeOut > SOCKET_TIME_OUT_UPPER_LIMIT) {
            throw new Exception();
        }
        return doPost(url, requestBody, contentType, headers, getHttpClient(socketTimeOut));
    }


    /**
     * 通用Post远程服务请求
     * @param url
     * 	请求url地址
     * @param requestBody
     * 	请求体body
     * @param contentType
     * 	内容类型
     * @param headers
     * 	请求头
     * @return String 业务自行解析
     * @throws Exception
     */
    public static String doPost(String url, String requestBody, ContentType contentType, List<BasicHeader> headers,
                                CloseableHttpClient client) throws Exception {

        // 构造http方法,设置请求和传输超时时间,重试3次
        CloseableHttpResponse response = null;
        long startTime = System.currentTimeMillis();
        try {
            HttpPost post = new HttpPost(url);
            if (!CollectionUtils.isEmpty(headers)) {
                for (BasicHeader header : headers) {
                    post.setHeader(header);
                }
            }
            StringEntity entity =
                    new StringEntity(requestBody, ContentType.create(contentType.getMimeType(), DEFAULT_CHAR_SET));
            post.setEntity(entity);
            response = client.execute(post);
            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
                throw new Exception();
            }
            String result = EntityUtils.toString(response.getEntity());
            return result;
        } finally {
            releaseResourceAndLog(url, requestBody, response, startTime);
        }
    }

    /**
     * 删除
     *
     * @param url     url
     * @param headers 标题
     * @return {@link String }
     * @throws Exception 例外
     */
    public static String doDelete(String url, List<BasicHeader> headers) throws Exception {

        // 构造http方法,设置请求和传输超时时间,重试3次
        CloseableHttpResponse response = null;
        long startTime = System.currentTimeMillis();
        try {
            HttpDelete delete = new HttpDelete(url);
            if (!CollectionUtils.isEmpty(headers)) {
                for (BasicHeader header : headers) {
                    delete.setHeader(header);
                }
            }
            response =  getHttpClient().execute(delete);
            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
                throw new Exception();
            }
            String result = EntityUtils.toString(response.getEntity());
            return result;
        } finally {
            releaseResourceAndLog(url, null, response, startTime);
        }
    }

    /**
     * 通用Post远程服务请求
     * @param url
     * 	请求url地址
     * @param requestBody
     * 	请求体body
     * @param contentType
     * 	内容类型
     * @param headers
     * 	请求头
     * @return String 业务自行解析
     * @throws Exception
     */
    public static String doPatch(String url, String requestBody, ContentType contentType, List<BasicHeader> headers
                                ) throws Exception {

        // 构造http方法,设置请求和传输超时时间,重试3次
        CloseableHttpResponse response = null;
        long startTime = System.currentTimeMillis();
        try {
            HttpPatch post = new HttpPatch(url);
            if (!CollectionUtils.isEmpty(headers)) {
                for (BasicHeader header : headers) {
                    post.setHeader(header);
                }
            }
            StringEntity entity =
                    new StringEntity(requestBody, ContentType.create(contentType.getMimeType(), DEFAULT_CHAR_SET));
            post.setEntity(entity);
            response = getHttpClient().execute(post);
            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
                throw new Exception();
            }
            String result = EntityUtils.toString(response.getEntity());
            return result;
        } finally {
            releaseResourceAndLog(url, requestBody, response, startTime);
        }
    }


    /**
     * 暂时用于智慧园区业务联调方式
     * @param url 业务请求url
     * @param param 业务参数
     * @return
     * @throws Exception
     */
    public static String doPostWithUrlEncoded(String url,
                                              Map<String, String> param) throws Exception {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = getHttpClient();
        CloseableHttpResponse response = null;
        long startTime = System.currentTimeMillis();
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建参数列表
            if (param != null) {
                List<org.apache.http.NameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // 模拟表单
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, DEFAULT_CHAR_SET);
                httpPost.setEntity(entity);
            }
            // 执行http请求
            response = httpClient.execute(httpPost);
            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
                throw new Exception();
            }
            String resultString = EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET);
            return resultString;
        } finally {
            releaseResourceAndLog(url, param == null ? null : param.toString(), response, startTime);
        }
    }

    private static void releaseResourceAndLog(String url, String request, CloseableHttpResponse response, long startTime) {
        if (null != response) {
            try {
                response.close();
                recordInterfaceLog(startTime, url, request);
            } catch (IOException e) {
            }
        }
    }

    public static String doGet(String url) throws Exception {
        return doGet(url, ContentType.DEFAULT_TEXT);
    }

    public static String doGet(String url, ContentType contentType) throws Exception {
        return doGet(url, contentType, null);
    }

    public static String doGet(String url, List<BasicHeader> headers) throws Exception {
        return doGet(url, ContentType.DEFAULT_TEXT, headers);
    }

    public static String doPost(String postUrl, File file){
        try {
            URL url = new URL(postUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "multipart/form-data");

            // 创建数据输出流
            DataOutputStream dos = new DataOutputStream(connection.getOutputStream());
            String lineEnd = "\r\n";
            String twoHyphens = "--";
            String boundary = "*****";

            String fileName = file.getName();
            dos.writeBytes(twoHyphens + boundary + lineEnd);
            dos.writeBytes("Content-Disposition: form-data; name=media; filename=\""
                    + fileName + "\"" + lineEnd);
            dos.writeBytes(lineEnd);

            FileInputStream fStream = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fStream);
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                dos.write(buffer, 0, bytesRead);
            }
            bis.close();
            fStream.close();

            // 结束数据部分
            dos.writeBytes(lineEnd);
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            // 关闭输出流
            dos.flush();
            dos.close();

            // 获取响应码
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            // 关闭连接
            connection.disconnect();
            return response.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通用Get远程服务请求
     * @param url
     * 	请求参数
     * @param contentType
     * 	请求参数类型
     * @param headers
     * 	请求头可以填充
     * @return String 业务自行解析数据
     * @throws Exception
     */
    public static String doGet(String url, ContentType contentType, List<BasicHeader> headers) throws Exception {
        CloseableHttpResponse response = null;
        long startTime = System.currentTimeMillis();
        try {
            CloseableHttpClient client = getHttpClient();
            HttpGet httpGet = new HttpGet(url);
            if (!CollectionUtils.isEmpty(headers)) {
                for (BasicHeader header : headers) {
                    httpGet.setHeader(header);
                }
            }
            if(contentType != null){
                httpGet.setHeader("Content-Type", contentType.getMimeType());
            }
            response = client.execute(httpGet);
            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
                throw new Exception();
            }
            String result = EntityUtils.toString(response.getEntity());
            return result;
        } finally {
            releaseResourceAndLog(url, null, response, startTime);
        }
    }

    private static void recordInterfaceLog(long startTime, String url, String request) {
        long endTime = System.currentTimeMillis();
        long timeCost = endTime - startTime;
    }
}
