package com.goomood.phm.utils;


import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Description: HttpUtils
 * @Author: GaoKun Wang
 * @Date: 2024/7/30
 */
@Slf4j
public class HttpUtils {
    private static final Integer DEFAULT_CONNECT_TIMEOUT = 3000;

    private static final Integer DEFAULT_REQUEST_TIMEOUT = 50000;

    private static final Integer MAX_IN_MEMORY_SIZE = 100 * 1024 * 1024;

    /**
     * get请求解析成字符串
     *
     * @param url url
     * @return java.lang.String
     */
    public static ClientResponse getResponse(String url) {
        Mono<ClientResponse> resp = createWebClientWithConnectAndReadTimeOuts().get().uri(url).exchangeToMono(Mono::just);
        return resp.block();
    }

    /**
     * get请求，解析成对象
     *
     * @param url     url
     * @param tClass  class
     * @param headers 请求头
     * @return T
     */
    public static <T> T get(String url, Class<T> tClass, Map<String, String> headers) {
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().get().uri(url).headers(t -> t.setAll(headers))
            .retrieve().bodyToMono(tClass).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * get请求，解析成对象
     *
     * @param url     url
     * @param headers 请求头
     * @return T
     */
    public static String get(String url, Map<String, String> headers) {
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().get().uri(url).headers(t -> t.setAll(headers))
            .retrieve().bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * get请求，解析成对象
     *
     * @param scheme  协议 http/https
     * @param host    host
     * @param obj     query params
     * @param headers 请求头
     * @return T
     */
    public static String get(String scheme, String host, String path, Object obj, Map<String, String> headers) {
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().get()
            .uri(uriBuilder -> uriBuilder.scheme(scheme).host(host).path(path).queryParams(getRequestParamMapByObj(obj))
                .build())
            .headers(t -> t.setAll(headers)).retrieve().bodyToMono(String.class)
            .timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * get请求，解析成对象
     *
     * @param url    url
     * @param tClass class
     * @return T
     */
    public static <T> T get(String url, Object obj, Class<T> tClass) {
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().get()
            .uri(uriBuilder -> uriBuilder.path(url).queryParams(getRequestParamMapByObj(obj)).build()).retrieve()
            .bodyToMono(tClass).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * get请求，解析成对象
     *
     * @param url    url
     * @param tClass class
     * @return T
     */
    public static <T> T get(String url, Class<T> tClass) {
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().get().uri(url).retrieve().bodyToMono(tClass)
            .timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * get请求解析成字符串
     *
     * @param url url
     * @return java.lang.String
     */
    public static String get(String url) {
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().get().uri(url).retrieve()
            .bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post表单请求返回对象
     *
     * @param url    url
     * @param params 请求参数
     * @param tClass 返回对象
     * @return T
     */
    public static <T> T post(String url, Map<String, String> params, Class<T> tClass) {
        MultiValueMap<String, String> formData = getRequestParamMap(params);
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_FORM_URLENCODED).body(BodyInserters.fromFormData(formData)).retrieve()
            .bodyToMono(tClass).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post表单请求返回字符串
     *
     * @param url    url
     * @param params 请求参数
     * @return java.lang.String
     */
    public static String post(String url, Map<String, String> params) {
        MultiValueMap<String, String> formData = getRequestParamMap(params);
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_FORM_URLENCODED).body(BodyInserters.fromFormData(formData)).retrieve()
            .bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post json请求结果解析成对象
     *
     * @param url      url
     * @param jsonBody 请求body，可以是对象或者是map
     * @param tClass   解析对象
     * @return T
     */
    public static <T> T postJson(String url, Object jsonBody, Class<T> tClass) {
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_JSON).body(Mono.just(jsonBody), Object.class).retrieve()
            .bodyToMono(tClass).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post json请求结果解析成对象
     *
     * @param url      url
     * @param jsonBody 请求body，可以是对象或者是map
     * @param tClass   解析对象
     * @return T
     */
    public static <T> T postJson(String url, Map<String, String> headers, Object jsonBody, Class<T> tClass) {
        Mono<T> resp =
            createWebClientWithConnectAndReadTimeOuts().post().uri(url).contentType(MediaType.APPLICATION_JSON)
                .headers(t -> t.setAll(headers)).body(Mono.just(jsonBody), Object.class).retrieve().bodyToMono(tClass)
                .timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post json请求结果解析成字符串
     *
     * @param url      url
     * @param jsonBody 请求body，可以是对象或者是map
     * @return java.lang.String
     */
    public static String postJson(String url, Object jsonBody) {
        log.info("请求参数:{}", JSONObject.parseObject(JSON.toJSONString(jsonBody)));
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_JSON).body(Mono.just(jsonBody), Object.class).retrieve()
            .bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    /**
     * post json请求结果解析成字符串
     *
     * @param url      url
     * @param jsonBody 请求body，可以是对象或者是map
     * @return java.lang.String
     */
    public static String postJson(String url, Map<String, String> headers, Object jsonBody) {
        Mono<String> resp =
            createWebClientWithConnectAndReadTimeOuts().post().uri(url).contentType(MediaType.APPLICATION_JSON)
                .headers(t -> t.setAll(headers)).body(Mono.just(jsonBody), Object.class).retrieve()
                .bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));
        return resp.block();
    }

    public static <T> T postRawJson(String url, String jsonBody, Class<T> tClass) {
        Mono<T> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(jsonBody)).retrieve()
            .bodyToMono(tClass).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));

        return resp.block();
    }

    public static String postRawJson(String url, String jsonBody) {
        Mono<String> resp = createWebClientWithConnectAndReadTimeOuts().post().uri(url)
            .contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(jsonBody)).retrieve()
            .bodyToMono(String.class).timeout(Duration.ofMillis(DEFAULT_REQUEST_TIMEOUT));

        return resp.block();
    }

    private static WebClient createWebClientWithConnectAndReadTimeOuts() {
        // 创建 reactor netty HTTP client
        HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, DEFAULT_CONNECT_TIMEOUT).doOnConnected(connection ->
            connection.addHandlerLast(new ReadTimeoutHandler(DEFAULT_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS))
        );
        // 使用上述 HTTP 客户端创建客户端 HTTP 连接器
        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
        // 使用此配置的 HTTP 连接器构建 Web 客户端
        return WebClient.builder()
            .exchangeStrategies(ExchangeStrategies.builder()
                .codecs(
                    clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(MAX_IN_MEMORY_SIZE))
                .build())
            .clientConnector(connector).build();
    }

    private static MultiValueMap<String, String> getRequestParamMap(Map<String, String> params) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            queryParams.add(entry.getKey(), entry.getValue());
        }
        return queryParams;
    }

    private static MultiValueMap<String, String> getRequestParamMapByObj(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> map = objectMapper.convertValue(obj, new TypeReference<>() {
        });
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (Objects.isNull(entry.getValue())) {
                continue;
            }
            queryParams.add(entry.getKey(), String.valueOf(entry.getValue()));
        }
        return queryParams;
    }
}
