package tech.easyflow.common.ai.plugin;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import okhttp3.Dispatcher;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 使用共享连接池、有界并发和实际响应字节限制调用 HTTP 插件。
 */
public final class PluginHttpClient {

    private static final int TIMEOUT_MILLIS = 10_000;
    private static final long DEFAULT_MAX_RESPONSE_BYTES =
            64L * 1024L * 1024L;
    private static final int MAX_TRACKED_HOSTS = 512;
    private static final Semaphore GLOBAL_PERMITS =
            new Semaphore(32, true);
    private static final Semaphore OVERFLOW_HOST_PERMITS =
            new Semaphore(8, true);
    private static final Map<String, Semaphore> HOST_PERMITS =
            new ConcurrentHashMap<>();
    private static final Object HOST_REGISTRY_LOCK = new Object();
    private static final OkHttpClient CLIENT = buildClient(true);
    private static final OkHttpClient NO_RETRY_CLIENT = buildClient(false);

    private PluginHttpClient() {
    }

    /**
     * 发送插件请求。
     *
     * @param url 插件 URL
     * @param method HTTP 方法
     * @param headers 默认请求头
     * @param pluginParams 插件参数
     * @return JSON 响应
     */
    public static JSONObject sendRequest(
            String url,
            String method,
            Map<String, Object> headers,
            List<PluginParam> pluginParams) {
        String normalizedMethod = method == null
                ? "GET"
                : method.trim().toUpperCase();
        List<PluginParam> safeParams = pluginParams == null
                ? List.of()
                : pluginParams;
        HttpUrl processedUrl = buildUrl(url, safeParams);
        Request.Builder requestBuilder = new Request.Builder().url(processedUrl);
        applyHeaders(requestBuilder, headers, safeParams);
        RequestBody requestBody = buildRequestBody(
                normalizedMethod, safeParams);
        applyMethod(requestBuilder, normalizedMethod, requestBody);

        Semaphore hostPermits = hostSemaphore(processedUrl.host());
        boolean hostAcquired = false;
        boolean globalAcquired = false;
        try {
            hostAcquired = hostPermits.tryAcquire(
                    1L, TimeUnit.SECONDS);
            if (!hostAcquired) {
                throw new IllegalStateException(
                        "插件目标并发已达上限: " + processedUrl.host());
            }
            globalAcquired = GLOBAL_PERMITS.tryAcquire(
                    1L, TimeUnit.SECONDS);
            if (!globalAcquired) {
                throw new IllegalStateException(
                        "插件 HTTP 并发已达上限");
            }
            OkHttpClient client = isIdempotent(normalizedMethod)
                    ? CLIENT
                    : NO_RETRY_CLIENT;
            try (Response response =
                         client.newCall(requestBuilder.build()).execute()) {
                ResponseBody body = response.body();
                if (body == null) {
                    throw new IllegalStateException("插件响应内容为空");
                }
                long maxBytes = Long.getLong(
                        "easyflow.plugin.http.max-response-bytes",
                        DEFAULT_MAX_RESPONSE_BYTES);
                String responseText = readBounded(body, maxBytes);
                return JSONUtil.parseObj(responseText);
            }
        } catch (InterruptedException error) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException(
                    "插件 HTTP 请求被中断", error);
        } catch (IOException error) {
            throw new IllegalStateException(
                    "插件 HTTP 请求失败", error);
        } finally {
            if (globalAcquired) {
                GLOBAL_PERMITS.release();
            }
            if (hostAcquired) {
                hostPermits.release();
            }
        }
    }

    /**
     * 创建共享 OkHttp 客户端。
     *
     * @param retryOnConnectionFailure 是否允许连接级自动恢复
     * @return 共享客户端
     */
    private static OkHttpClient buildClient(
            boolean retryOnConnectionFailure) {
        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(32);
        dispatcher.setMaxRequestsPerHost(8);
        return new OkHttpClient.Builder()
                .dispatcher(dispatcher)
                .connectTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .readTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .writeTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .callTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(retryOnConnectionFailure)
                .build();
    }

    /**
     * 构造带路径参数和查询参数的 URL。
     *
     * @param url 原始 URL
     * @param params 插件参数
     * @return 完整 URL
     */
    private static HttpUrl buildUrl(
            String url, List<PluginParam> params) {
        String processed = replacePathVariables(url, params);
        HttpUrl parsed = HttpUrl.parse(processed);
        if (parsed == null) {
            throw new IllegalArgumentException(
                    "插件 URL 不合法: " + processed);
        }
        HttpUrl.Builder builder = parsed.newBuilder();
        for (PluginParam param : params) {
            if (param.isEnabled()
                    && "query".equalsIgnoreCase(param.getMethod())
                    && param.getDefaultValue() != null) {
                builder.addQueryParameter(
                        param.getName(),
                        stringify(param.getDefaultValue()));
            }
        }
        return builder.build();
    }

    /**
     * 合并默认请求头和参数请求头。
     *
     * @param builder 请求构造器
     * @param headers 默认请求头
     * @param params 插件参数
     */
    private static void applyHeaders(
            Request.Builder builder,
            Map<String, Object> headers,
            List<PluginParam> params) {
        if (headers != null) {
            headers.forEach((name, value) -> {
                if (name != null && value != null) {
                    builder.header(name, String.valueOf(value));
                }
            });
        }
        for (PluginParam param : params) {
            if (param.isEnabled()
                    && "header".equalsIgnoreCase(param.getMethod())
                    && param.getDefaultValue() != null) {
                builder.header(
                        param.getName(),
                        String.valueOf(param.getDefaultValue()));
            }
        }
    }

    /**
     * 构造 JSON 或 multipart 请求体。
     *
     * @param method HTTP 方法
     * @param params 插件参数
     * @return 请求体；无请求体时为 {@code null}
     */
    private static RequestBody buildRequestBody(
            String method, List<PluginParam> params) {
        if (!supportsRequestBody(method)) {
            return null;
        }
        Map<String, Object> bodyValues = new HashMap<>();
        boolean multipart = false;
        for (PluginParam param : params) {
            if (!param.isEnabled()
                    || !"body".equalsIgnoreCase(param.getMethod())) {
                continue;
            }
            Object value = buildNestedParamValue(param);
            bodyValues.put(param.getName(), value);
            multipart |= value instanceof MultipartFile;
        }
        if (bodyValues.isEmpty()) {
            return RequestBody.create(
                    new byte[0], null);
        }
        if (!multipart) {
            return RequestBody.create(
                    JSONUtil.toJsonStr(bodyValues),
                    MediaType.parse("application/json; charset=utf-8"));
        }
        MultipartBody.Builder builder =
                new MultipartBody.Builder().setType(MultipartBody.FORM);
        for (Map.Entry<String, Object> entry : bodyValues.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                MediaType contentType = MediaType.parse(
                        file.getContentType() == null
                                ? "application/octet-stream"
                                : file.getContentType());
                builder.addFormDataPart(
                        entry.getKey(),
                        Objects.toString(
                                file.getOriginalFilename(),
                                entry.getKey()),
                        streamingFileBody(file, contentType));
            } else {
                builder.addFormDataPart(
                        entry.getKey(), stringify(value));
            }
        }
        return builder.build();
    }

    /**
     * 创建从 MultipartFile 流式读取的请求体，避免 getBytes 二次复制。
     *
     * @param file 文件参数
     * @param mediaType 内容类型
     * @return 流式请求体
     */
    private static RequestBody streamingFileBody(
            MultipartFile file, MediaType mediaType) {
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return mediaType;
            }

            @Override
            public long contentLength() {
                return file.getSize();
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                try (InputStream inputStream = file.getInputStream()) {
                    byte[] buffer = new byte[64 * 1024];
                    int read;
                    while ((read = inputStream.read(buffer)) != -1) {
                        sink.write(buffer, 0, read);
                    }
                }
            }
        };
    }

    /**
     * 应用 HTTP 方法。
     *
     * @param builder 请求构造器
     * @param method HTTP 方法
     * @param body 请求体
     */
    private static void applyMethod(
            Request.Builder builder,
            String method,
            RequestBody body) {
        if ("GET".equals(method)) {
            builder.get();
        } else if ("HEAD".equals(method)) {
            builder.head();
        } else if ("DELETE".equals(method) && body == null) {
            builder.delete();
        } else {
            builder.method(method, body);
        }
    }

    /**
     * 按实际字节读取响应。
     *
     * @param body 响应体
     * @param maxBytes 最大字节数
     * @return UTF-8 响应
     * @throws IOException 读取失败或响应超限
     */
    private static String readBounded(
            ResponseBody body, long maxBytes) throws IOException {
        if (maxBytes > 0L
                && body.contentLength() > maxBytes) {
            throw new IOException(
                    "插件响应超过字节上限: " + maxBytes);
        }
        try (InputStream inputStream = body.byteStream();
             ByteArrayOutputStream outputStream =
                     new ByteArrayOutputStream(8 * 1024)) {
            byte[] buffer = new byte[64 * 1024];
            long total = 0L;
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                total += read;
                if (maxBytes > 0L && total > maxBytes) {
                    throw new IOException(
                            "插件响应超过字节上限: " + maxBytes);
                }
                outputStream.write(buffer, 0, read);
            }
            return outputStream.toString(StandardCharsets.UTF_8);
        }
    }

    /**
     * 递归构造对象参数。
     *
     * @param param 参数
     * @return 参数值
     */
    private static Object buildNestedParamValue(
            PluginParam param) {
        if (!"Object".equalsIgnoreCase(param.getType())) {
            return param.getDefaultValue();
        }
        Map<String, Object> nested = new HashMap<>();
        if (param.getChildren() != null) {
            for (PluginParam child : param.getChildren()) {
                if (child.isEnabled()) {
                    nested.put(
                            child.getName(),
                            buildNestedParamValue(child));
                }
            }
        }
        return nested;
    }

    /**
     * 替换 URL 路径变量。
     *
     * @param url 原始 URL
     * @param params 插件参数
     * @return 替换后的 URL
     */
    private static String replacePathVariables(
            String url, List<PluginParam> params) {
        String result = Objects.requireNonNull(
                url, "插件 URL 不能为空");
        for (PluginParam param : params) {
            if (param.isEnabled()
                    && "path".equalsIgnoreCase(param.getMethod())
                    && param.getDefaultValue() != null) {
                result = result.replace(
                        "{" + param.getName() + "}",
                        String.valueOf(param.getDefaultValue()));
            }
        }
        return result;
    }

    /**
     * 获取主机隔离许可并严格限制注册表体积。
     *
     * @param host 主机名
     * @return 主机许可
     */
    private static Semaphore hostSemaphore(String host) {
        Semaphore existing = HOST_PERMITS.get(host);
        if (existing != null) {
            return existing;
        }
        synchronized (HOST_REGISTRY_LOCK) {
            existing = HOST_PERMITS.get(host);
            if (existing != null) {
                return existing;
            }
            if (HOST_PERMITS.size() >= MAX_TRACKED_HOSTS) {
                return OVERFLOW_HOST_PERMITS;
            }
            Semaphore created = new Semaphore(8, true);
            HOST_PERMITS.put(host, created);
            return created;
        }
    }

    /**
     * 判断方法是否幂等。
     *
     * @param method HTTP 方法
     * @return 是否允许连接级恢复
     */
    private static boolean isIdempotent(String method) {
        return "GET".equals(method)
                || "HEAD".equals(method)
                || "OPTIONS".equals(method);
    }

    /**
     * 判断方法是否支持请求体。
     *
     * @param method HTTP 方法
     * @return 是否支持请求体
     */
    private static boolean supportsRequestBody(String method) {
        return "POST".equals(method)
                || "PUT".equals(method)
                || "PATCH".equals(method);
    }

    /**
     * 将普通或嵌套值转换为表单字符串。
     *
     * @param value 参数值
     * @return 表单值
     */
    private static String stringify(Object value) {
        if (value == null) {
            return "";
        }
        if (value instanceof String
                || value instanceof Number
                || value instanceof Boolean) {
            return String.valueOf(value);
        }
        return JSONUtil.toJsonStr(value);
    }
}
