package com.cyksj.common.util;

import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

/**
 * debug jvm params:
 *   1. httpClient:
 *     -Djdk.internal.httpclient.debug=true
 *   2. webSocket:
 *     -Djdk.internal.httpclient.websocket.debug=true
 *   3. hpack
 *     -Djdk.internal.httpclient.hpack.debug=true
 *
 * <p>
 *     jdk11 Http Client is Thread-safe
 *     Build connection pool and selector thread pool internally
 * </p>
 *
 * @author valor.
 */
public class J11HttpC {

    private J11HttpC() { }

    private static final J11HttpC instance;
    static {
        instance = new J11HttpC();
    }

    /* ---------- register http client (Editable) ---------- */

    /**
     * 连接超时时间
     */
    private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(120);

    /**
     * 读取超时时间
     */
    private static final Duration SOCKET_TIMEOUT = Duration.ofSeconds(120);

    /**
     * Thread-safe
     */
    private static final HttpClient defaultClient;
    static {
        defaultClient = HttpClient.newBuilder()
                .connectTimeout(CONNECT_TIMEOUT)
                .build();
    }

    /**
     * 微信退款 专用 ssl http client
     */
    private static final HttpClient wx_refund;
    static {
        wx_refund = HttpClient.newBuilder()
                .connectTimeout(CONNECT_TIMEOUT)
                .sslContext(NSslKit.KeyCert.wechat_refund.getSslContext())
                .build();
    }

    /**
     * http clients
     */
    public enum Client {
        BASE(defaultClient),
        WX_REFUND(wx_refund),
        ;

        private HttpClient symbol;

        Client(HttpClient symbol) {
            this.symbol = symbol;
        }
    }

    /* ---------- http request builder ---------- */

    public static Builder custom() {
        return new Builder();
    }

    public static class Builder {
        private String url;
        private Method method;
        private KVAdapter[] headers;
        private HttpRequest.BodyPublisher bodyPublisher;

        /**
         * cache URI
         */
        private boolean cacheURI;

        private Builder() {
            this.cacheURI = false;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder cacheURI() {
            this.cacheURI = true;
            return this;
        }

        public Builder method(Method method) {
            this.method = method;
            return this;
        }

        public Builder ofGet() {
            this.method = Method.GET;
            return this;
        }

        public Builder ofPost() {
            this.method = Method.POST;
            return this;
        }

        public Builder headers(ReqType reqType) {
            this.headers = new KVAdapter[] {ofHeader("content-type", reqType.mime)};
            return this;
        }

        public Builder headers(KVAdapter... headers) {
            this.headers = headers;
            return this;
        }

        public Builder headers(ReqType reqType, KVAdapter... headers) {
            final int oldLength = headers.length;
            final int newLength = oldLength+ 1;
            KVAdapter[] newHeaders = new KVAdapter[newLength];
            System.arraycopy(headers, 0, newHeaders, 1, oldLength);

            newHeaders[0] = ofHeader("content-type", reqType.mime);
            this.headers = newHeaders;
            return this;
        }

        public Builder body(HttpRequest.BodyPublisher bodyPublisher) {
            this.bodyPublisher = bodyPublisher;
            return this;
        }

        /* --- 同步send --- */

        public <T> HttpResponse<T> send(HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
            return this.send(Client.BASE, responseBodyHandler);
        }

        public <T> HttpResponse<T> send(Client httpClient, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
            return instance.send(httpClient.symbol, this.getRequest(), responseBodyHandler);
        }

        /* --- 异步send --- */

        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpResponse.BodyHandler<T> responseBodyHandler) {
            return this.sendAsync(Client.BASE, responseBodyHandler);
        }

        public <T> CompletableFuture<HttpResponse<T>> sendAsync(Client httpClient, HttpResponse.BodyHandler<T> responseBodyHandler) {
            return instance.sendAsync(httpClient.symbol, this.getRequest(), responseBodyHandler);
        }

        /* --- ------- --- */

        private HttpRequest getRequest() {
            // require http URL
            if (StringUtils.isBlank(this.url)) {
                throw new RuntimeException("Empty Http URL.");
            }

            // default http method: GET
            if (this.method == null) {
                this.method = Method.GET;
            }
            // default http request body: noBody
            if (this.bodyPublisher == null) {
                this.bodyPublisher = HttpRequest.BodyPublishers.noBody();
            }

            URI uri = getURI(this.url, this.cacheURI);
            HttpRequest request;
            if (this.headers == null || this.headers.length <= 0) {
                request = instance.buildRequest(uri, this.method, this.bodyPublisher);
            } else {
                request = instance.buildRequest(uri, this.method, this.bodyPublisher, this.headers);
            }
            return request;
        }

    }

    /* ---------- cache http request uri ---------- */

    /**
     * 缓存URI的数量
     */
//    private static final int URI_CACHE_SIZE = 25;

    /**
     * 减少URI类的解析 LRU cache
     */
//    private static volatile LruCache<String, URI> uriCache = new LruCache<>(URI_CACHE_SIZE);

    /**
     * 获取URI
     */
    private static URI getURI(String url, boolean cache) {
        // 非常频繁的接口调用 会导致cache维护开销增加
//        if (cache) {
//            URI uri = uriCache.get(url);
//            if (null == uri) {
//                uri = URI.create(url);
//                uriCache.put(url, uri);
//            }
//            return uri;
//        }
        return URI.create(url);
    }

    /* ---------- pack http client ---------- */

    /**
     * 构建request
     *
     * @param uri           {@link URI}
     * @param headers       {@link J11HttpC.KVAdapter}
     * @param httpMethod    {@link J11HttpC.Method}
     * @param bodyPublisher {@link HttpRequest.BodyPublisher}
     * @return {@link HttpRequest}
     */
    private HttpRequest buildRequest(URI uri,
                                     Method httpMethod,
                                     HttpRequest.BodyPublisher bodyPublisher,
                                     KVAdapter... headers) {

        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.timeout(SOCKET_TIMEOUT);
        builder.uri(uri);

        // 设置请求头
        if (headers.length > 0) {
            for (KVAdapter e : headers) {
                builder.setHeader(e.name, e.value);
            }
        }

        // 设置请求方式和body参数
        builder.method(httpMethod.symbol, bodyPublisher);
        return builder.build();
    }

    /**
     * 同步send
     *
     * @see HttpClient#send(HttpRequest, HttpResponse.BodyHandler)
     */
    public <T> HttpResponse<T>
    send(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
            throws IOException, InterruptedException {
        return httpClient.send(request, responseBodyHandler);
    }

    /**
     * 同步send
     *
     * @see HttpClient#sendAsync(HttpRequest, HttpResponse.BodyHandler)
     */
    public <T> CompletableFuture<HttpResponse<T>>
    sendAsync(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) {
        return httpClient.sendAsync(request, responseBodyHandler);
    }

    /* ---------- http extensions ---------- */

    public static KVAdapter ofHeader(String name, String value) {
        return new KVAdapter(name, value);
    }

    /**
     * Http Request Header
     */
    public static final class KVAdapter {
        private String name;
        private String value;

        private KVAdapter(String name, String value) {
            this.name = name;
            this.value = value;
        }
    }

    public enum Method {
        GET("GET"),
        POST("POST"),
        HEAD("HEAD"),
        OPTIONS("OPTIONS"),
        PUT("PUT"),
        DELETE("DELETE"),
        TRACE("TRACE");

        private String symbol;

        Method(String symbol) {
            this.symbol = symbol;
        }
    }

    public enum ReqType {
//        form_data("multipart/form-data"),
        form_urlencoded("application/x-www-form-urlencoded; charset=utf-8"),
        raw_text("text/plain; charset=utf-8"),
        raw_js("application/javascript; charset=utf-8"),
        raw_json("application/json; charset=utf-8"),
        raw_html("text/html; charset=utf-8"),
        raw_xml("application/xml; charset=utf-8");
//        binary("application/octet-stream");

        private String mime;

        ReqType(String mime) {
            this.mime = mime;
        }
    }

}
