package com.zm.order.tool.server.utils;

import com.alibaba.fastjson.JSON;
import org.apache.http.*;
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.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Http请求
 *
 * @author 宋欢
 */
public class HttpUtils {

    private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    public static class Result {

        /**
         * Http状态码
         */
        private int statusCode;

        /**
         * 返回结果
         */
        private String response;

        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }

        public boolean isStatusOk() {
            return getStatusCode() == HttpStatus.OK.value();
        }

        public int getStatusCode() {
            return statusCode;
        }

        public void setStatusCode(int statusCode) {
            this.statusCode = statusCode;
        }

        public String getResponse() {
            return response;
        }

        public void setResponse(String response) {
            this.response = response;
        }
    }

    /**
     * 请求方式
     */
    public enum MethodEnum {
        /**
         * GET
         */
        GET(1, "GET"),

        /**
         * POST
         */
        POST(2, "POST");

        private int code;
        private String message;

        MethodEnum(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

    /**
     * 内容类型
     */
    public enum ContentTypeEnum {
        /**
         * 表单
         */
        URL_ENCODED(1, "application/x-www-form-urlencoded"),

        /**
         * Json
         */
        JSON(2, "application/json");

        private int code;
        private String message;

        ContentTypeEnum(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

    /**
     * Send Http Request
     */
    public static Result req(@NotNull CloseableHttpClient httpClient, @NotNull MethodEnum method, @NotBlank String url,
                             Map<String, Object> params, Map<String, String> headers,
                             ContentTypeEnum contentType) throws HttpException {
        Result result = new Result();

        CloseableHttpResponse httpResponse;
        try {
            httpResponse = doReq(httpClient, method, url, params, headers, contentType);
        } catch (Throwable tr) {
            throw new HttpException("req() failure", tr);
        }

        if (httpResponse == null) {
            throw new HttpException("req() fai");
        }

        int statusCode = httpResponse.getStatusLine().getStatusCode();
        result.setStatusCode(statusCode);

        HttpEntity httpEntity = httpResponse.getEntity();
        if (httpEntity != null) {
            try {
                String responseStr = EntityUtils.toString(httpEntity, Consts.UTF_8);
                result.setResponse(responseStr);
            } catch (IOException e) {
                throw new HttpException(e.getMessage());
            } finally {
                consumeHttpEntity(httpEntity);
                closeHttpResponse(httpResponse);
            }
        }

        closeHttpResponse(httpResponse);
        return result;
    }

    /**
     * Send Http Request
     */
    public static CloseableHttpResponse doReq(@NotNull CloseableHttpClient httpClient, @NotNull MethodEnum method, @NotBlank String url,
                                              Map<String, Object> params, Map<String, String> headers,
                                              ContentTypeEnum contentType) throws MethodNotSupportedException, URISyntaxException, IOException {
        int methodCode = method.getCode();

        if (methodCode == MethodEnum.GET.getCode()) {
            return doGet(httpClient, url, params, headers);
        } else if (methodCode == MethodEnum.POST.getCode()) {
            return doPost(httpClient, url, params, contentType, headers);
        }

        throw new MethodNotSupportedException("unsupported method: " + method.getMessage() + ", method must be GET or POST");
    }

    /**
     * Send Http GET
     */
    public static CloseableHttpResponse doGet(@NotNull CloseableHttpClient httpClient, @NotBlank String url,
                                              Map<String, Object> params, Map<String, String> headers) throws URISyntaxException, IOException {
        HttpGet httpGet = getHttpGetInstance(url, params, headers);
        return httpClient.execute(httpGet);
    }

    /**
     * Send Http POST
     */
    public static CloseableHttpResponse doPost(@NotNull CloseableHttpClient httpClient, @NotBlank String url,
                                               Map<String, Object> params, ContentTypeEnum contentType, Map<String, String> headers) throws IOException {
        HttpPost httpPost = getHttpPostInstance(url, params, contentType, headers);
        return httpClient.execute(httpPost);
    }

    /**
     * New Http GET
     */
    public static HttpGet getHttpGetInstance(@NotBlank String url, Map<String, Object> params,
                                             Map<String, String> headers) throws URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);

        if (params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                Object value = entry.getValue();
                uriBuilder.setParameter(entry.getKey(), value == null ? "" : value.toString());
            }
        }

        URI uri = uriBuilder.build();
        HttpGet httpGet = new HttpGet(uri.toString());

        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpGet.addHeader(entry.getKey(), entry.getValue());
            }
        }

        return httpGet;
    }

    /**
     * New Http POST
     */
    public static HttpPost getHttpPostInstance(@NotBlank String url, Map<String, Object> params,
                                               ContentTypeEnum contentType, Map<String, String> headers) {
        HttpPost httpPost = new HttpPost(url);

        if (contentType != null) {
            httpPost.addHeader(HTTP.CONTENT_TYPE, contentType.getMessage());

            if (params != null) {
                HttpEntity httpEntity = null;

                int typeCode = contentType.getCode();

                if (typeCode == ContentTypeEnum.JSON.getCode()) {
                    httpEntity = getJsonEntity(params);
                } else if (typeCode == ContentTypeEnum.URL_ENCODED.getCode()) {
                    httpEntity = getUrlEncodedFormEntity(params);
                }

                if (httpEntity != null) {
                    httpPost.setEntity(httpEntity);
                }
            }
        }

        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpPost.addHeader(entry.getKey(), entry.getValue());
            }
        }

        return httpPost;
    }

    /**
     * 请求参数转Json Entity
     */
    public static HttpEntity getJsonEntity(@NotNull Map<String, Object> params) {
        String jsonStr = JSON.toJSONString(params);
        return new StringEntity(jsonStr, ContentType.APPLICATION_JSON);
    }

    /**
     * 请求参数转Url Encoded Form Entity
     */
    public static HttpEntity getUrlEncodedFormEntity(@NotNull Map<String, Object> params) {
        List<NameValuePair> parameters = joinParams(params);
        return new UrlEncodedFormEntity(parameters, Consts.UTF_8);
    }

    /**
     * 拼接请求参数
     */
    public static List<NameValuePair> joinParams(@NotNull Map<String, Object> params) {
        List<NameValuePair> result = new ArrayList<>();

        for (Map.Entry<String, Object> entry : params.entrySet()) {
            Object value = entry.getValue();
            BasicNameValuePair pair = new BasicNameValuePair(entry.getKey(), value == null ? "" : value.toString());
            result.add(pair);
        }

        return result;
    }

    /**
     * 销毁HttpResponse.Entity
     */
    public static void consumeHttpEntity(HttpEntity httpEntity) {
        if (httpEntity != null) {
            try {
                EntityUtils.consume(httpEntity);
            } catch (Throwable tr) {
                logger.error("consume httpEntity error: {}", tr.getMessage());
            }
        }
    }

    /**
     * 关闭HttpResponse
     */
    public static void closeHttpResponse(CloseableHttpResponse httpResponse) {
        if (httpResponse != null) {
            try {
                httpResponse.close();
            } catch (Throwable tr) {
                logger.error("close httpResponse error: {}", tr.getMessage());
            }
        }
    }

    /**
     * 关闭HttpClient
     */
    public static void closeHttpClient(CloseableHttpClient httpClient) {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (Throwable tr) {
                logger.error("close HttpClient error: {}", tr.getMessage());
            }
        }
    }

}
