package cn.iocoder.yudao.framework.http.core.client.impl;

import cn.iocoder.yudao.framework.http.collection.CollectionUtils;
import cn.iocoder.yudao.framework.http.collection.MapUtils;
import cn.iocoder.yudao.framework.http.convert.JsonUtils;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.exception.ExceptionUtils;
import cn.iocoder.yudao.framework.http.util.StrUtils;
import okhttp3.*;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.rmi.RemoteException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author Erwin Feng
 * @since 2023-02-09
 */
public class OkHttpClient implements HttpClient {

    private static final Logger log = LoggerFactory.getLogger(OkHttpClient.class);

    @Override
    public Response execute(Request request, Request.Option option) throws IOException {
        if (CollectionUtils.isNotEmpty(request.getFileList())) {
            return upload(request, option);
        }
        if (Request.Method.GET == request.getMethod()) {
            return get(request, option);
        } else if (Request.Method.POST == request.getMethod()) {
            return post(request, option);
        }
        throw new RemoteException("request method '" + request.getMethod().name() + "' not implemented");
    }

    private okhttp3.OkHttpClient client(Request.Option option) {
        okhttp3.OkHttpClient.Builder builder = new okhttp3.OkHttpClient.Builder();
        HostnameVerifier hostnameVerifier = null;
        SSLSocketFactory sslContextFactory = null;
        if (Objects.nonNull(option)) {
            Integer connectTimeoutSecond = getTimeoutSecond(option.getConnectTimeoutSecond());
            if (Objects.nonNull(connectTimeoutSecond)) {
                builder.connectTimeout(Duration.ofSeconds(connectTimeoutSecond));
            }
            Integer readTimeoutSecond = getTimeoutSecond(option.getReadTimeoutSecond());
            if (Objects.nonNull(readTimeoutSecond)) {
                builder.readTimeout(Duration.ofSeconds(readTimeoutSecond));
            }
            hostnameVerifier = option.getHostnameVerifier();
            sslContextFactory = option.getSslContextFactory();
        }
        if (Objects.isNull(hostnameVerifier)) {
            hostnameVerifier = getIgnoreSslHostnameVerifier();
        }
        if (Objects.isNull(sslContextFactory)) {
            sslContextFactory = getIgnoreInitedSslContext().getSocketFactory();
        }
        builder.sslSocketFactory(sslContextFactory, IGNORE_SSL_TRUST_MANAGER_X509);
        builder.hostnameVerifier(hostnameVerifier);
        return builder.build();
    }

    private Response get(Request request, Request.Option option) {
        okhttp3.OkHttpClient client = client(option);
        okhttp3.Request httpRequest = buildRequest(getUrl(request), getHeaderMap(option), request.getMethod(), null);
        return call(client, httpRequest);
    }

    private Response post(Request request, Request.Option option) {
        okhttp3.OkHttpClient client = client(option);
        Request.ParamFormat paramFormat = request.getParamFormat();
        Map<String, Object> param = request.getParam();
        RequestBody requestBody = null;
        if (MapUtils.isNotEmpty(param)) {
            if (param.containsKey(PARAM_DEFAULT_KEY)) {
                String paramStr = param.get(PARAM_DEFAULT_KEY) + "";
                if (Request.ParamFormat.JSON == paramFormat) {
                    requestBody = buildJsonRequestBody(paramStr);
                }else if(Request.ParamFormat.STRING == paramFormat){
                    requestBody = buildStringRequestBody(paramStr);
                    System.out.println("buildStringRequestBody");
                }
            } else {
                if (Request.ParamFormat.JSON == paramFormat) {
                    requestBody = buildJsonRequestBody(JsonUtils.string(param));
                } else if (Request.ParamFormat.FORM == paramFormat) {
                    requestBody = buildFormRequestBody(param);
                }
            }
        }
        okhttp3.Request httpRequest = buildRequest(getUrl(request), getHeaderMap(option), request.getMethod(), requestBody);
        return call(client, httpRequest);
    }

    private RequestBody buildJsonRequestBody(String param) {
        return RequestBody.create(
                param,
                MediaType.parse("application/json")
        );
    }

    private RequestBody buildStringRequestBody(String param) {
        return RequestBody.create(
                param,
                MediaType.parse("application/x-www-form-urlencoded")

        );
    }

    private RequestBody buildFormRequestBody(Map<String, Object> param) {
        FormBody.Builder builder = new FormBody.Builder();
        if (MapUtils.isNotEmpty(param)) {
            for (Map.Entry<String, Object> entry : param.entrySet()) {
                builder.add(entry.getKey(), entry.getValue() + "");
            }
        }
        return builder.build();
    }

    private okhttp3.Request buildRequest(String url, Map<String, String> headers, Request.Method requestMethod, RequestBody requestBody) {
        okhttp3.Request.Builder builder = new okhttp3.Request.Builder();
        builder.url(url);
        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }
        if (Objects.nonNull(requestBody)) {
            if (Request.Method.POST == requestMethod) {
                builder.post(requestBody);
            } else if (Request.Method.PUT == requestMethod) {
                builder.put(requestBody);
            }
        }
        return builder.build();
    }

    private Response call(okhttp3.OkHttpClient client, okhttp3.Request request) {
        Call call = client.newCall(request);
        try (okhttp3.Response httpResponse = call.execute()) {
            return convertResponse(httpResponse);
        } catch (IOException e) {
            log.error(ExceptionUtils.getStackTrace(e));
        }
        return null;
    }

    private Response convertResponse(okhttp3.Response httpResponse) throws IOException {
        if (Objects.isNull(httpResponse)) {
            throw new RuntimeException("http response null");
        }
        Response response = new Response();
        response.setCode(httpResponse.code());
        response.setMsg(httpResponse.message());
        ResponseBody responseBody = httpResponse.body();
        if (Objects.nonNull(responseBody)) {
            response.setBody(responseBody.string());
        }
        return response;
    }

    private HttpUrl getHttpUrl(Request request) {
        HttpUrl.Builder builder = HttpUrl.get(request.getUrl()).newBuilder();

        if (Request.Method.GET == request.getMethod()) {
            if (MapUtils.isNotEmpty(request.getParam())) {
                if (request.getParam().containsKey(PARAM_DEFAULT_KEY)) {
                    builder.addPathSegment(request.getParam().get(PARAM_DEFAULT_KEY) + "");
                } else {
                    for (Map.Entry<String, Object> entry : request.getParam().entrySet()) {
                        builder.addQueryParameter(entry.getKey(), entry.getValue() + "");
                    }
                }
            }
        }

        return builder.build();
    }

    private String getUrl(Request request) {

        if (StrUtils.isBlank(request.getUrl())) {
            throw new RuntimeException("url不能为空");
        }

        StringBuilder url = new StringBuilder(request.getUrl());

        if (Request.Method.GET == request.getMethod()) {
            if (!url.toString().contains("?")) {
                url.append("?");
            }
            if (MapUtils.isNotEmpty(request.getParam())) {
                if (request.getParam().containsKey(PARAM_DEFAULT_KEY)) {
                    url.append(request.getParam().get(PARAM_DEFAULT_KEY));
                } else {
                    for (Map.Entry<String, Object> entry : request.getParam().entrySet()) {
                        url.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
                    }
                }
            }
        }

        String result = url.toString();
        if (result.endsWith("&")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    /**
     * 文件上传
     */
    private Response upload(Request request, Request.Option option) {
        okhttp3.OkHttpClient client = client(option);

        // 创建 MediaType 对象
        MediaType mediaType = MediaType.parse("multipart/form-data; charset=utf-8");

        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
        bodyBuilder.setType(MultipartBody.FORM);

        List<Request.FileBo> fileList = request.getFileList();
        for (Request.FileBo fileBo : fileList) {
            if (Objects.isNull(fileBo.getFile())) {
                throw new RuntimeException("upload failed: file must not be null");
            }
            if (StrUtils.isBlank(fileBo.getFileName())) {
                fileBo.setFileName(fileBo.getFile().getName());
            }
            bodyBuilder.addFormDataPart(fileBo.getParamName(), fileBo.getFileName(), RequestBody.create(fileBo.getFile(),mediaType));
        }
        if (MapUtils.isNotEmpty(request.getParam())) {
            for (Map.Entry<String, Object> entry : request.getParam().entrySet()) {
                bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue() + "");
            }
        }
        RequestBody requestBody = bodyBuilder.build();

        okhttp3.Request httpRequest = buildRequest(getUrl(request), getHeaderMap(option), request.getMethod(), requestBody);
        return call(client, httpRequest);
    }

    private Map<String, String> getHeaderMap(Request.Option option) {
        if (Objects.isNull(option)) {
            return null;
        }
        return option.getHeaders();
    }

    /**
     * Get initialized SSLContext instance which ignored SSL certification
     *
     * @return SSLContext
     */
    @SneakyThrows
    public static SSLContext getIgnoreInitedSslContext() {
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new SecureRandom());
        return sslContext;
    }

    private static final TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                }

                @Override
                public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                }

                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[]{};
                }
            }
    };

    /**
     * Get HostnameVerifier which ignored SSL certification
     * @return HostnameVerifier
     */
    public static HostnameVerifier getIgnoreSslHostnameVerifier() {
        return (hostname, sslSession ) -> true;
    }

    public static final X509TrustManager IGNORE_SSL_TRUST_MANAGER_X509 = new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] {};
        }
    };

}
