package com.face.global.config;


import com.face.global.consts.GlobalConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.MDC;

import java.io.IOException;
import java.time.Duration;
import java.util.Optional;
import java.util.UUID;

@Slf4j
public class HttpClientFactory {
    private static final String REQUEST_START_TIME_KEY = "requestStartTime";

    private static final String REQUEST_START_URI_KEY = "requestStartUri";

    private static final String REQUEST_START_MARK_KEY = "requestStartMark";

    public static CloseableHttpClient getHttpClient(ProxyProperties proxyProperties,
                                                    HttpClientProperties httpClientProperties) {
        PoolingHttpClientConnectionManager clientConnectionManager = buildConnectionManager(httpClientProperties);
        return HttpClients.custom().setProxy(getProxy(proxyProperties))
                .setConnectionManager(clientConnectionManager)
                .addRequestInterceptorLast((request, entity, context) -> {
                    String traceId = MDC.get(GlobalConstant.TRACE_ID_KEY);
                    if (StringUtils.isBlank(traceId)) {
                        traceId = UUID.randomUUID().toString().replaceAll("-", "");
                    }
                    request.setHeader(GlobalConstant.TRACE_ID_KEY, traceId);
                    long timestamp =  System.currentTimeMillis();
                    String mark = UUID.randomUUID().toString().replace("-", "");
                    context.setAttribute(REQUEST_START_TIME_KEY, timestamp);
                    context.setAttribute(REQUEST_START_TIME_KEY, timestamp);
                    context.setAttribute(REQUEST_START_MARK_KEY, mark);
                    context.setAttribute(REQUEST_START_URI_KEY, request.getRequestUri());
                    log.info("startRequest {} {} {} {}", request.getRequestUri(), request.getHeaders(), timestamp, mark);
                })
                .addResponseInterceptorFirst((response, entity, context) -> {
                    Long requestTime = (Long) context.getAttribute(REQUEST_START_TIME_KEY);
                    Long timeCost = null;
                    if (requestTime != null && requestTime > 0) {
                        timeCost = System.currentTimeMillis() - requestTime;
                    }
                    log.info("endRequest {} {} {} {} {} {}", context.getAttribute(REQUEST_START_URI_KEY), requestTime,
                            context.getAttribute(REQUEST_START_MARK_KEY), response.getCode(), response.getHeaders(), timeCost);
                })
                .setRetryStrategy(new DefaultHttpRequestRetryStrategy() {
                    @Override
                    public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) {
                        log.warn("retryRequestCountForRequest {} {} {} {} {}", request.getRequestUri(),
                                context.getAttribute(REQUEST_START_TIME_KEY),
                                context.getAttribute(REQUEST_START_MARK_KEY),
                                httpClientProperties.getRetryCount(), execCount, exception);
                        if (httpClientProperties.getRetryCount() < execCount) {
                            return false;
                        }
                        return true;
                    }

                    @Override
                    public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {
                        log.warn("retryRequestCountForResponse {} {} {} {} {} {}",  context.getAttribute(REQUEST_START_URI_KEY),
                                context.getAttribute(REQUEST_START_TIME_KEY),
                                context.getAttribute(REQUEST_START_MARK_KEY),
                                response.getHeaders(), response.getCode(), execCount);
                        return false;
                    }
                }).build();

    }

    private static HttpHost getProxy(ProxyProperties proxyProperties) {
        if (proxyProperties == null || StringUtils.isBlank(proxyProperties.getHost())) {
            return null;
        }
        return new HttpHost(proxyProperties.getHost(), proxyProperties.getPort());
    }

    private static PoolingHttpClientConnectionManager buildConnectionManager(HttpClientProperties httpClientProperties) {
        return PoolingHttpClientConnectionManagerBuilder.create()
                .setConnectionConfigResolver(route -> ConnectionConfig.custom()
                        .setConnectTimeout(Timeout.of(Optional.ofNullable(httpClientProperties.getConnectTimeout()).orElse(Duration.ofSeconds(3))))
                        .setSocketTimeout(Timeout.of(Optional.ofNullable(httpClientProperties.getSocketTimeout()).orElse(Duration.ofSeconds(30))))
                        .setValidateAfterInactivity(Timeout.of(Optional.ofNullable(httpClientProperties.getValidateAfterInactivity())
                                .orElse(Duration.ofSeconds(30))))
                        .setTimeToLive(Timeout.of(Optional.ofNullable(httpClientProperties.getTimeToLive()).orElse(Duration.ofSeconds(300))))
                        .build())
                        .setMaxConnTotal(httpClientProperties.getMaxConnTotal())
                        .setMaxConnPerRoute(httpClientProperties.getMaxConnPerRoute())
                .setSSLSocketFactory(new SSLConnectionSocketFactory(SSLContexts.createDefault(), (s, sslSession) -> true))
                .build();
    }
}
