package com.njzscloud.common.core.http;

import cn.hutool.core.collection.CollUtil;
import com.njzscloud.common.core.http.constant.HttpMethod;
import com.njzscloud.common.core.http.support.ResponseInfo;
import com.njzscloud.common.core.tuple.Tuple2;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * HTTP 客户端
 *
 * @see okhttp3.OkHttpClient
 */
@Slf4j
public final class HttpClient {
    private static volatile HttpClient DEFAULT;

    private final OkHttpClient OK_HTTP_CLIENT;

    public HttpClient(HttpClientProperties httpClientProperties) {
        this(httpClientProperties, null);
    }

    public HttpClient(OkHttpClient okHttpClient) {
        OK_HTTP_CLIENT = okHttpClient;
    }

    public HttpClient(HttpClientProperties httpClientProperties, ExecutorService executorService) {
        Duration callTimeout = httpClientProperties.getCallTimeout();
        Duration keepAliveTime = httpClientProperties.getKeepAliveTime();
        long keepAliveTimeSeconds = keepAliveTime.getSeconds();
        int maxIdleConnections = httpClientProperties.getMaxIdleConnections();
        Duration readTimeout = httpClientProperties.getReadTimeout();
        Duration connectTimeout = httpClientProperties.getConnectTimeout();

        ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveTimeSeconds, TimeUnit.SECONDS);

        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .callTimeout(callTimeout)
                .readTimeout(readTimeout)
                .connectTimeout(connectTimeout)
                .connectionPool(connectionPool);
        if (executorService != null) {
            Dispatcher dispatcher = new Dispatcher(executorService);
            builder.dispatcher(dispatcher);
        }

        this.OK_HTTP_CLIENT = builder.build();
    }

    private HttpClient() {
        this(HttpClientProperties.DEFAULT);
    }

    public static synchronized HttpClient defaultHttpClient() {
        if (DEFAULT == null) DEFAULT = new HttpClient();
        return DEFAULT;
    }

    /* private Request buildRequest(HttpServer httpServer,
                                 HttpEndpoint httpEndpoint,
                                 RequestParamBuilder requestParamBuilder,
                                 RequestInterceptor requestInterceptor) {


        if (requestInterceptor != null) {
            RequestParam processed = requestInterceptor.process(httpServer, httpEndpoint, requestParamBuilder);
            if (processed != null) requestParam = processed;
        }

        HttpMethod httpMethod = httpEndpoint.httpMethod;
        String url = httpEndpoint.urlTpl;
        BodyParam<?> bodyParam = requestParam.bodyParam;


        Map<String, Object> pathParamMap = requestParam.pathParam.getParam();
        if (CollUtil.isNotEmpty(pathParamMap)) {
            url = StrUtil.format(url, pathParamMap);
        }

        Map<String, Object> queryParamMap = requestParam.queryParam.getParam();
        String query = Param.kvStr(queryParamMap);
        if (StrUtil.isNotBlank(query)) {
            query = "?" + query;
        }
        url = url + query;

        Headers headers = null;
        Map<String, Object> headerParamMap = requestParam.headerParam.getParam();
        if (CollUtil.isNotEmpty(headerParamMap)) {
            Headers.Builder headerBuilder = new Headers.Builder();
            Set<Map.Entry<String, Object>> entries = headerParamMap.entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value != null) headerBuilder.set(key, value.toString());
            }
            headers = headerBuilder.build();
        }


        RequestBody postBody = null;
        if (httpMethod != HttpMethod.GET) {
            byte[] param = bodyParam.getParam();
            postBody = RequestBody.create(param, MediaType.parse(bodyParam.contentType));
        }

        Request.Builder requestBuilder = new Request.Builder()
                .url(httpServer + url)
                .method(httpMethod.name(), postBody);

        if (headers != null) {
            requestBuilder.headers(headers);
        }

        return requestBuilder.build();
    }

    public <T> T execute(
            HttpServer httpServer,
            HttpEndpoint endpoint,
            RequestParamBuilder requestParamBuilder,
            Type responseType,
            RequestInterceptor requestInterceptor,
            ResponseInterceptor<T> responseInterceptor
    ) {
        ResponseResult.ResponseResultBuilder responseResultBuilder = ResponseResult.builder();

        Response response = null;
        ResponseBody responseBody = null;
        Integer code = null;
        String message = null;
        Headers headers = null;
        byte[] body = new byte[0];

        try {
            Request request = buildRequest(httpServer, endpoint, requestParamBuilder, requestInterceptor);
            Call call = this.OK_HTTP_CLIENT.newCall(request);
            response = call.execute();
            responseBody = response.body();
            if (responseBody != null) {
                body = responseBody.bytes();
            }
            code = response.code();
            message = response.message();
            headers = response.headers();

            responseResultBuilder.
                    code(code)
                    .status(message)
                    .headers(headers.toMultimap())
                    .body(body)
                    .build();
        } catch (Exception e) {
            log.error("", e);
            responseResultBuilder.e(e);
        } finally {
            if (responseBody != null) responseBody.close();
            if (response != null) response.close();
        }

        return responseInterceptor.process(
                responseType,
                httpServer, endpoint, requestParam,
                responseResultBuilder.build());
    } */


    public ResponseInfo execute(HttpMethod httpMethod, String url, Map<String, String> headers, Tuple2<String, byte[]> body) {
        Request.Builder requestBuilder = new Request.Builder().url(url);

        if (httpMethod != HttpMethod.GET) {
            requestBuilder.method(httpMethod.name(),
                    RequestBody.create(body.get_1(),
                            MediaType.parse(body.get_0())
                    ));
        }

        if (CollUtil.isNotEmpty(headers)) {
            Headers.Builder headerBuilder = new Headers.Builder();
            headers.forEach(headerBuilder::set);
            requestBuilder.headers(headerBuilder.build());
        }

        try (
                Response response = this.OK_HTTP_CLIENT
                        .newCall(requestBuilder.build())
                        .execute();
                ResponseBody responseBody = response.body()
        ) {
            int code = response.code();
            String message = response.message();
            Map<String, List<String>> responseHeaders = response.headers().toMultimap();
            byte[] bytes = responseBody == null ? new byte[0] : responseBody.bytes();
            return ResponseInfo.create(code, message, responseHeaders, bytes);
        } catch (Exception e) {
            log.error("", e);
            return ResponseInfo.create(0, "http 请求失败", null, null);
        }
    }
}
