package com.agent.management.engine.hermes;

import com.agent.management.config.HermesProperties;
import com.agent.management.engine.ExecutionLog;
import com.agent.management.engine.HermesRunResult;
import com.agent.management.engine.NodeStreamSink;
import com.agent.management.service.HermesErrorPatternService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * Hermes Bridge HTTP 客户端。
 * 调用 Python Bridge 的 /run 端点，解析 SSE 流式响应。
 * 仅在 hermes.enabled=true 时激活。
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "hermes.enabled", havingValue = "true")
public class HermesBridgeClient {

    /**
     * LLM/智能体调用专用 logger：
     * 由 logback-spring.xml 中 LLM_HTTP logger 配置为 DEBUG 级别，仅写入独立日志文件，不输出到控制台。
     */
    private static final Logger LLM_HTTP = LoggerFactory.getLogger("LLM_HTTP");

    private final HermesProperties properties;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 错误响应嗅探服务（方案 B）。
     *
     * <p>使用 {@link Autowired#required(boolean)} 标记为非必需：
     * 测试场景或 Hermes 关闭场景下允许为 null，{@code #parseSseResponse} 会跳过嗅探。</p>
     */
    private final HermesErrorPatternService errorPatternService;

    @Autowired
    public HermesBridgeClient(HermesProperties properties,
                              @Autowired(required = false) HermesErrorPatternService errorPatternService) {
        this.properties = properties;
        this.errorPatternService = errorPatternService;
    }

    /**
     * 兼容旧调用方与测试：保留无嗅探服务的构造器。
     */
    public HermesBridgeClient(HermesProperties properties) {
        this(properties, null);
    }

    /**
     * 调用 Hermes Agent 执行任务（简单版本）。
     *
     * @deprecated M-5：推荐使用 {@link HermesRunRequest} 参数对象 + {@link #run(HermesRunRequest)}，
     * 避免多参数重载导致的参数顺序混淆。本重载保留仅为向后兼容。
     */
    @Deprecated
    public HermesRunResult run(String systemPrompt, String userMessage, int maxIterations) throws Exception {
        return run(HermesRunRequest.of(systemPrompt, userMessage, maxIterations));
    }

    /**
     * @deprecated M-5：同上，推荐使用 {@link HermesRunRequest}。
     */
    @Deprecated
    public HermesRunResult run(String systemPrompt, String userMessage, int maxIterations,
                                String hermesHome, String workingDir) throws Exception {
        return run(HermesRunRequest.of(systemPrompt, userMessage, maxIterations)
                .hermesHome(hermesHome).workingDir(workingDir));
    }

    /**
     * @deprecated M-5：同上，推荐使用 {@link HermesRunRequest}。
     */
    @Deprecated
    public HermesRunResult run(String systemPrompt, String userMessage, int maxIterations,
                                String hermesHome, String workingDir,
                                String nodeId, NodeStreamSink sink) throws Exception {
        return run(HermesRunRequest.of(systemPrompt, userMessage, maxIterations)
                .hermesHome(hermesHome).workingDir(workingDir).nodeId(nodeId).sink(sink));
    }

    /**
     * @deprecated M-5：同上，推荐使用 {@link HermesRunRequest}。
     */
    @Deprecated
    public HermesRunResult run(String systemPrompt, String userMessage, int maxIterations,
                                String hermesHome, String workingDir,
                                String nodeId, NodeStreamSink sink, String sessionId) throws Exception {
        return run(HermesRunRequest.of(systemPrompt, userMessage, maxIterations)
                .hermesHome(hermesHome).workingDir(workingDir)
                .nodeId(nodeId).sink(sink).sessionId(sessionId));
    }

    /**
     * @deprecated M-5：同上，推荐使用 {@link HermesRunRequest}。
     */
    @Deprecated
    public HermesRunResult run(String systemPrompt, String userMessage, int maxIterations,
                                String hermesHome, String workingDir,
                                String nodeId, NodeStreamSink sink, String sessionId,
                                Map<String, String> modelConfig) throws Exception {
        return run(HermesRunRequest.of(systemPrompt, userMessage, maxIterations)
                .hermesHome(hermesHome).workingDir(workingDir)
                .nodeId(nodeId).sink(sink).sessionId(sessionId).modelConfig(modelConfig));
    }

    /**
     * 主入口：使用参数对象 {@link HermesRunRequest} 调用 Hermes Agent。
     *
     * <p>M-5：合并原 5 个重载为单一入口，避免参数顺序混淆。
     * 旧重载保留为兼容入口，内部全部委托给本方法。</p>
     */
    public HermesRunResult run(HermesRunRequest req) throws Exception {
        String url = "http://127.0.0.1:" + properties.getBridge().getPort() + "/run";

        // 未显式指定时回落到固定 HERMES_HOME（hermes.bridge.home），
        // 保证 Bridge 侧 agent 池缓存键与技能扫描目录稳定一致
        String effectiveHermesHome = (req.hermesHome != null && !req.hermesHome.isBlank())
                ? req.hermesHome
                : properties.getBridge().getResolvedHome().toString();

        // H-3：请求 body 是单线程构建 + 序列化，无需并发容器，改用 LinkedHashMap
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("user_message", req.userMessage);
        body.put("max_iterations", req.maxIterations);
        // 注入「可用技能列表 + 高效执行策略」前置 prompt，减少 LLM 猜名失败与上下文膨胀
        String enhancedSystemPrompt = buildEnhancedSystemPrompt(effectiveHermesHome, req.systemPrompt);
        if (enhancedSystemPrompt != null && !enhancedSystemPrompt.isBlank()) {
            body.put("system_prompt", enhancedSystemPrompt);
        }
        body.put("hermes_home", effectiveHermesHome);
        if (req.workingDir != null && !req.workingDir.isBlank()) {
            body.put("working_dir", req.workingDir);
        }
        if (req.sessionId != null && !req.sessionId.isBlank()) {
            body.put("session_id", req.sessionId);
        }
        if (req.modelConfig != null) {
            if (req.modelConfig.get("modelId") != null && !req.modelConfig.get("modelId").isBlank()) {
                body.put("model_id", req.modelConfig.get("modelId"));
            }
            if (req.modelConfig.get("baseUrl") != null && !req.modelConfig.get("baseUrl").isBlank()) {
                body.put("base_url", req.modelConfig.get("baseUrl"));
            }
            if (req.modelConfig.get("apiKey") != null && !req.modelConfig.get("apiKey").isBlank()) {
                body.put("api_key", req.modelConfig.get("apiKey"));
            }
            if (req.modelConfig.get("modelName") != null && !req.modelConfig.get("modelName").isBlank()) {
                body.put("model_name", req.modelConfig.get("modelName"));
            }
        }

        byte[] bodyBytes = objectMapper.writeValueAsBytes(body);

        HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("Accept", "text/event-stream");
        // M5: 注入 X-Bridge-Token 共享密钥，由 Bridge 端校验
        String authToken = properties.getBridge().getAuthToken();
        if (authToken != null && !authToken.isBlank()) {
            conn.setRequestProperty("X-Bridge-Token", authToken);
        }
        conn.setConnectTimeout(10000);
        // v1.1：readTimeout 改为 24 小时（兜底），实际 Askfor 长等待由 SSE 心跳 + 前端重连机制保证。
        // 原 5 分钟超时无法支撑用户离开几小时后再回来回答的场景。
        conn.setReadTimeout(86_400_000);

        // === 请求 DEBUG 日志（仅写入 LLM 日志文件，不输出到控制台） ===
        LLM_HTTP.debug("[Hermes-REQ] ========== 请求开始 ==========");
        LLM_HTTP.debug("[Hermes-REQ] POST {}", url);
        LLM_HTTP.debug("[Hermes-REQ] nodeId={}, sessionId={}, maxIterations={}", req.nodeId, req.sessionId, req.maxIterations);
        LLM_HTTP.debug("[Hermes-REQ] Body ({} bytes): {}", bodyBytes.length, new String(bodyBytes, StandardCharsets.UTF_8));

        long startTime = System.currentTimeMillis();
        conn.getOutputStream().write(bodyBytes);
        conn.getOutputStream().flush();

        int responseCode = conn.getResponseCode();
        // connectMs = 建立连接 + 写出 body + 收到 HTTP 响应头的时间
        // （注意：SSE 的响应头几乎立刻到达，body 还在流式传输，所以这里耗时很短）
        long connectMs = System.currentTimeMillis() - startTime;
        if (responseCode != 200) {
            String error = readErrorStream(conn);
            long totalMs = connectMs;
            LLM_HTTP.debug("[Hermes-RESP] HTTP {} (total={}ms), error: {}", responseCode, totalMs, error);
            throw new RuntimeException("Hermes Bridge 返回 HTTP " + responseCode + ": " + error);
        }

        long streamStart = System.currentTimeMillis();
        HermesRunResult result = parseSseResponse(conn, req.nodeId, req.sink);
        // streamMs = 读取 SSE 流式 body 的耗时（这才是真正的 Agent 执行时间，包含所有 LLM 调用与工具循环）
        long streamMs = System.currentTimeMillis() - streamStart;
        long totalMs = System.currentTimeMillis() - startTime;

        // === 响应 DEBUG 日志（分别记录连接耗时与流式读取耗时，便于定位卡点） ===
        LLM_HTTP.debug("[Hermes-RESP] HTTP 200, connect={}ms, stream={}ms, total={}ms, finalText 长度={}, logs 数={}",
                connectMs, streamMs, totalMs,
                result.getFinalText() == null ? 0 : result.getFinalText().length(),
                result.getLogs() == null ? 0 : result.getLogs().size());
        LLM_HTTP.debug("[Hermes-RESP] finalText: {}", result.getFinalText());
        LLM_HTTP.debug("[Hermes-RESP] ========== 请求结束 ==========");
        return result;
    }

    /**
     * H-2：消费 errorStream，避免 keep-alive 连接被 stale。
     * 任何异常都吞掉（调用方已知 code != 200，只关心 error 文本作为日志）。
     */
    private static String readErrorStream(HttpURLConnection conn) {
        InputStream es = null;
        try {
            es = conn.getErrorStream();
            if (es == null) return "";
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(es, StandardCharsets.UTF_8))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) sb.append(line);
                return sb.toString();
            }
        } catch (IOException e) {
            return "";
        } finally {
            // 消费完后立即断开，释放底层 socket（避免 keep-alive 残留）
            conn.disconnect();
        }
    }

    /**
     * M-5：Hermes Agent 运行请求参数对象（不可变 + fluent setter）。
     *
     * <p>用法：</p>
     * <pre>{@code
     * HermesRunResult r = client.run(HermesRunRequest.of(prompt, msg, 100)
     *         .hermesHome("/data/.hermes")
     *         .workingDir(workDir)
     *         .nodeId(nodeId)
     *         .sink(sink)
     *         .sessionId(sessionKey)
     *         .modelConfig(modelConfig));
     * }</pre>
     */
    public static final class HermesRunRequest {
        private final String systemPrompt;
        private final String userMessage;
        private final int maxIterations;
        private String hermesHome;
        private String workingDir;
        private String nodeId;
        private NodeStreamSink sink;
        private String sessionId;
        private Map<String, String> modelConfig;

        private HermesRunRequest(String systemPrompt, String userMessage, int maxIterations) {
            this.systemPrompt = systemPrompt;
            this.userMessage = userMessage;
            this.maxIterations = maxIterations;
        }

        public static HermesRunRequest of(String systemPrompt, String userMessage, int maxIterations) {
            return new HermesRunRequest(systemPrompt, userMessage, maxIterations);
        }

        public HermesRunRequest hermesHome(String v) { this.hermesHome = v; return this; }
        public HermesRunRequest workingDir(String v) { this.workingDir = v; return this; }
        public HermesRunRequest nodeId(String v) { this.nodeId = v; return this; }
        public HermesRunRequest sink(NodeStreamSink v) { this.sink = v; return this; }
        public HermesRunRequest sessionId(String v) { this.sessionId = v; return this; }
        public HermesRunRequest modelConfig(Map<String, String> v) { this.modelConfig = v; return this; }
    }

    /**
     * 解析 SSE 响应流，收集日志并提取最终回复。
     * 若 sink 非 null：thinking 通过 sink 实时推送（不进 logs），工具事件同时进 logs 和 sink。
     * 若 sink 为 null：thinking 进 logs（兼容旧行为）。
     */
    private HermesRunResult parseSseResponse(HttpURLConnection conn, String nodeId, NodeStreamSink sink) throws Exception {
        StringBuilder finalResponse = new StringBuilder();
        List<ExecutionLog> logs = new ArrayList<>();

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {

            String eventType = "";
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("event: ")) {
                    eventType = line.substring(7).trim();
                } else if (line.startsWith("data: ")) {
                    String dataJson = line.substring(6);
                    JsonNode data = objectMapper.readTree(dataJson);

                    switch (eventType) {
                        case "tool_start" -> {
                            String tool = data.path("tool").asText("?");
                            String args = data.has("args") ? objectMapper.writeValueAsString(data.get("args")) : null;
                            String argsTrunc = truncate(args, 500);
                            logs.add(ExecutionLog.toolCall(tool, argsTrunc));
                            if (sink != null) {
                                try {
                                    sink.emit(nodeId, "tool_call", argsTrunc, tool);
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 tool_call 失败: {}", ex.getMessage());
                                }
                            }
                            log.debug("[Hermes] 工具调用: {}", tool);
                        }
                        case "tool_end" -> {
                            String tool = data.path("tool").asText("?");
                            String summary = data.path("result_summary").asText("");
                            String summaryTrunc = truncate(summary, 500);
                            logs.add(ExecutionLog.toolResult(tool, summaryTrunc));
                            if (sink != null) {
                                try {
                                    sink.emit(nodeId, "tool_result", summaryTrunc, tool);
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 tool_result 失败: {}", ex.getMessage());
                                }
                            }
                            log.debug("[Hermes] 工具完成: {}", tool);
                        }
                        case "text" -> {
                            String delta = data.path("content").asText("");
                            if (!delta.isEmpty()) {
                                // thinking 既要实时推送（流式 UI），也要进入 logs（运行记录可回放）
                                logs.add(ExecutionLog.thinking(delta));
                                if (sink != null) {
                                    try {
                                        sink.emit(nodeId, "thinking", delta, null);
                                    } catch (Exception ex) {
                                        log.warn("[Hermes] sink 推送 thinking 失败: {}", ex.getMessage());
                                    }
                                }
                            }
                        }
                        // v1.1：每轮 LLM 调用前的进度（iteration + prev_tools）
                        case "step" -> {
                            int iteration = data.path("iteration").asInt(0);
                            String prevToolsJson = data.has("prev_tools")
                                    ? objectMapper.writeValueAsString(data.get("prev_tools")) : "[]";
                            logs.add(ExecutionLog.step(iteration, prevToolsJson));
                            if (sink != null) {
                                try {
                                    sink.emit(nodeId, "step",
                                            "{\"iteration\":" + iteration + ",\"prev_tools\":" + prevToolsJson + "}",
                                            null);
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 step 失败: {}", ex.getMessage());
                                }
                            }
                        }
                        // v1.1：实时状态文案（覆盖式，如"压缩上下文中"）
                        case "status" -> {
                            String message = data.path("message").asText("");
                            if (!message.isEmpty()) {
                                logs.add(ExecutionLog.status(message));
                                if (sink != null) {
                                    try {
                                        sink.emit(nodeId, "status", message, null);
                                    } catch (Exception ex) {
                                        log.warn("[Hermes] sink 推送 status 失败: {}", ex.getMessage());
                                    }
                                }
                            }
                        }
                        // v1.1：thinking_callback 透传（Hermes 思考增量，与 text 区分用于实时思考区展示）
                        case "thinking_status" -> {
                            String content = data.path("content").asText("");
                            if (!content.isEmpty()) {
                                if (sink != null) {
                                    try {
                                        // 前端按 kind=status 聚合到"实时思考"区
                                        sink.emit(nodeId, "status", content, null);
                                    } catch (Exception ex) {
                                        log.warn("[Hermes] sink 推送 thinking_status 失败: {}", ex.getMessage());
                                    }
                                }
                            }
                        }
                        // v1.1：工具细粒度进度（event_type + name + preview/args）
                        case "tool_progress" -> {
                            String tpEventType = data.path("event_type").asText("");
                            String toolName = data.path("name").asText(null);
                            String preview = data.has("preview")
                                    ? objectMapper.writeValueAsString(data.get("preview")) : null;
                            String detailJson = (preview != null) ? preview
                                    : (data.has("args") ? objectMapper.writeValueAsString(data.get("args")) : "{}");
                            logs.add(ExecutionLog.toolProgress(tpEventType, detailJson));
                            if (sink != null) {
                                try {
                                    sink.emit(nodeId, "tool_progress",
                                            "{\"event_type\":\"" + tpEventType + "\",\"name\":\"" + toolName
                                                    + "\",\"preview\":" + (preview != null ? preview : "null") + "}",
                                            toolName);
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 tool_progress 失败: {}", ex.getMessage());
                                }
                            }
                        }
                        // v1.1：todo 工具完整输出（前端解析后渲染 TODO List）
                        // Python 端 hermes_bridge.py 已用 json.dumps 把 result 序列化为字符串，
                        // 这里用 asText 取字符串原值，避免 writeAsString 再次转义导致前端二次 parse 失败
                        case "todo_update" -> {
                            String fullJson = data.path("result").asText("{}");
                            logs.add(ExecutionLog.todoUpdate(fullJson));
                            if (sink != null) {
                                try {
                                    sink.emit(nodeId, "todo_update", fullJson, "todo");
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 todo_update 失败: {}", ex.getMessage());
                                }
                            }
                        }
                        // v1.1：模型请求用户确认（Askfor 区域）
                        // 注意：clarify_request 不结束 SSE 读取循环，Bridge 在 Python 侧同步等待用户回答后
                        // 才会继续推送后续事件。这里只负责把请求转发给前端，不中断 readLine。
                        case "clarify_request" -> {
                            String clarifyId = data.path("clarify_id").asText("");
                            String question = data.path("question").asText("");
                            String choicesJson = data.has("choices")
                                    ? objectMapper.writeValueAsString(data.get("choices")) : "[]";
                            logs.add(ExecutionLog.clarifyRequest(clarifyId, question, choicesJson));
                            if (sink != null) {
                                try {
                                    // H-4：原手工拼接 JSON 缺乏对 \n / \\ 等特殊字符的转义，
                                    // 前端 JSON.parse 易失败。改用 ObjectMapper 构造保证 JSON 合法。
                                    com.fasterxml.jackson.databind.node.ObjectNode clarifyPayload =
                                            objectMapper.createObjectNode();
                                    clarifyPayload.put("clarify_id", clarifyId);
                                    clarifyPayload.put("question", question);
                                    clarifyPayload.set("choices", objectMapper.readTree(choicesJson));
                                    sink.emit(nodeId, "clarify_request",
                                            objectMapper.writeValueAsString(clarifyPayload), null);
                                } catch (Exception ex) {
                                    log.warn("[Hermes] sink 推送 clarify_request 失败: {}", ex.getMessage());
                                }
                            }
                            log.info("[Hermes] 请求用户确认 clarify_id={}, question={}", clarifyId, question);
                        }
                        case "done" -> finalResponse.append(data.path("content").asText(""));
                        case "error" -> {
                            String msg = data.path("message").asText("未知错误");
                            logs.add(ExecutionLog.error(msg));
                            throw new RuntimeException("Hermes Agent 执行错误: " + msg);
                        }
                        default -> {}
                    }
                    eventType = "";
                }
            }
        }

        String finalText = finalResponse.toString();

        // B: 错误响应嗅探 —— Hermes Agent / hermes-agent 底层把 LLM 失败描述当作 final_response 返回，
        // 这里通过可配置模式列表识别 4xx/限流/认证失败等典型错误，命中即抛 HermesErrorResponseException，
        // 由 HermesAgentExecutor / HermesSmartActionExecutor 转化为 NodeExecutionResult.failed
        if (errorPatternService != null) {
            HermesErrorPatternService.ErrorDetectionResult detection = errorPatternService.isErrorResponse(finalText);
            if (detection.isError()) {
                String errMsg = String.format("Hermes Agent 响应被识别为错误（命中模式「%s」）: %s",
                        detection.getMatchedPatternName(), truncate(finalText, 200));
                LLM_HTTP.warn("[Hermes-RESP] 错误响应嗅探命中: pattern={}, response(截断)={}",
                        detection.getMatchedPatternName(), truncate(finalText, 500));
                throw new HermesErrorResponseException(errMsg, detection.getMatchedPatternName());
            }
        }

        return new HermesRunResult(finalText, logs);
    }

    private String truncate(String text, int maxLen) {
        if (text == null) return null;
        return text.length() <= maxLen ? text : text.substring(0, maxLen) + "...";
    }

    /**
     * 高效执行策略前置 prompt。
     *
     * <p>用于解决 Hermes Agent 执行慢的几个根因（基于日志分析）：</p>
     * <ul>
     *   <li>② LLM 猜错技能名导致无效工具调用（readme-gen / git-commit-log-gen 等不存在）</li>
     *   <li>③ 中间轮次过度消化工具结果，每轮都生成几百 token 的「思考总结」</li>
     *   <li>④ read_file 整个大文件导致 in= token 雪球式膨胀（23k→50k）</li>
     *   <li>⑤ 探索完成后仍在循环验证，多出 2-3 轮无意义 LLM 调用</li>
     * </ul>
     *
     * <p>这是「软优化」——通过 prompt 引导 LLM 自主控制上下文，而非硬性截断。
     * 对比修改 hermes-agent 内部的 trajectory_compressor，这种方式风险最低、维护成本最小。</p>
     */
    private static final String EFFICIENCY_PROMPT_TEMPLATE =
            "=== 执行策略提示（必须严格遵守）===\n\n" +
            "## 可用技能（skill_view 调用时请使用精确名，禁止猜测）\n%s\n\n" +
            "## 技能根目录（构造脚本路径时必须使用，禁止凭记忆猜测）\n" +
            "所有技能文件位于：%s\n" +
            "技能的脚本/资源绝对路径 = 上述根目录 + /技能名/技能内相对路径，" +
            "例如技能 foo 的 scripts/run.py 的绝对路径为「技能根目录/foo/scripts/run.py」。\n" +
            "SKILL.md 中的相对路径均相对于该技能自身目录；terminal 的工作目录不是技能目录，" +
            "执行脚本前必须先按此规则拼接出完整绝对路径。\n\n" +
            "## 高效执行原则\n" +
            "1. 一次性产出：明确目标后，应一次性生成所有需要的文件/输出，" +
            "不要每完成一个小步骤就停下来生成中间总结，避免多消耗 1 轮 LLM 调用。\n" +
            "2. 摘要工具优先：当文件较大（>200 行）时，优先使用 head_file / search_files / grep " +
            "等摘要工具定位关键内容，避免直接 read_file 整个大文件——上下文膨胀会让后续每一轮都更慢。\n" +
            "3. 控制探索深度：达到目标即停止，不要重复读取已知信息、反复验证；" +
            "任务完成后用一句话告知「已完成」，不要生成冗长总结报告。\n" +
            "4. 工具结果复用：上一轮已经读到的内容，本轮不要重复读取；" +
            "如需引用，直接基于已读内容作答。\n\n" +
            "## 注意\n" +
            "上述原则是「在合适场景下」的优化指引。当任务确实需要逐项探索（如多文件批量分析）时，" +
            "可按需展开；但不要为了「显得认真」而增加无意义的中间步骤。\n";

    /**
     * 在原始 system_prompt 前追加「执行策略提示」。
     *
     * @param hermesHome  本次运行的 HERMES_HOME（其下应有 skills 子目录），用于扫描可用技能列表
     * @param basePrompt  调用方提供的原始 system_prompt（如 SKILL.md 内容），可为 null
     * @return 增强后的 system_prompt；若 basePrompt 为空且无可用技能列表，返回 null（不发 system_prompt）
     */
    private String buildEnhancedSystemPrompt(String hermesHome, String basePrompt) {
        String skillsList = scanAvailableSkills(hermesHome);
        String skillsRoot = Paths.get(hermesHome, "skills").toString();
        String efficiencySection = String.format(EFFICIENCY_PROMPT_TEMPLATE, skillsList, skillsRoot);

        if (basePrompt == null || basePrompt.isBlank()) {
            return efficiencySection;
        }
        return efficiencySection + "\n---\n\n" + basePrompt;
    }

    /**
     * 扫描 HERMES_HOME/skills 目录，返回逗号分隔的技能 folderName 列表。
     * 扫描失败时返回占位文案（不影响主流程）。
     *
     * <p>每次扫描开销很小（单次 Files.list，几十毫秒级），相对于 Hermes 整体上百秒的耗时可以忽略，
     * 因此不做缓存——这也避免了「运行时新增技能但缓存未更新」的问题。</p>
     */
    private String scanAvailableSkills(String hermesHome) {
        if (hermesHome == null || hermesHome.isBlank()) {
            return "（未提供 hermes_home，无法列出）";
        }
        Path skillsDir = Paths.get(hermesHome, "skills");
        if (!Files.isDirectory(skillsDir)) {
            return "（skills 目录不存在于 " + skillsDir + "）";
        }
        List<String> names = new ArrayList<>();
        try (Stream<Path> entries = Files.list(skillsDir)) {
            entries.filter(Files::isDirectory)
                    .map(p -> p.getFileName().toString())
                    .filter(s -> !s.startsWith(".") && !s.equals("__pycache__"))
                    .sorted()
                    .forEach(names::add);
        } catch (IOException e) {
            log.warn("[HermesBridgeClient] 扫描技能目录失败: {}", e.getMessage());
            return "（扫描失败: " + e.getMessage() + "）";
        }
        if (names.isEmpty()) {
            return "（无）";
        }
        return String.join(", ", names);
    }

    /**
     * v1.1：向 Bridge 提交用户对 clarify_request 的回答。
     *
     * <p>由 WorkflowController 在收到前端 POST /runs/{runId}/resume 时调用。
     * Bridge 收到后会调用 clarify_gateway.resolve_gateway_clarify，
     * 唤醒在 on_clarify 回调中阻塞等待的 Hermes Agent 线程。</p>
     *
     * @param clarifyId clarify_request 事件携带的 clarify_id
     * @param answer    用户回答（选项值或自定义文本）
     * @return true 表示 Bridge 报告已成功唤醒等待方；false 表示未找到对应 clarify 或已过期
     */
    public boolean submitClarifyAnswer(String clarifyId, String answer) {
        String url = "http://127.0.0.1:" + properties.getBridge().getPort()
                + "/clarify/" + clarifyId + "/answer";
        HttpURLConnection conn = null;
        try {
            // H-3：单线程构建请求体，用 LinkedHashMap 即可，无需 ConcurrentHashMap
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("answer", answer == null ? "" : answer);
            byte[] bodyBytes = objectMapper.writeValueAsBytes(body);

            // === 请求 DEBUG 日志（仅写入 LLM 日志文件，不输出到控制台） ===
            LLM_HTTP.debug("[Hermes-Clarify-REQ] ========== 请求开始 ==========");
            LLM_HTTP.debug("[Hermes-Clarify-REQ] POST {}", url);
            LLM_HTTP.debug("[Hermes-Clarify-REQ] Body ({} bytes): {}", bodyBytes.length,
                    new String(bodyBytes, StandardCharsets.UTF_8));

            conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/json");
            String authToken = properties.getBridge().getAuthToken();
            if (authToken != null && !authToken.isBlank()) {
                conn.setRequestProperty("X-Bridge-Token", authToken);
            }
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(5000);
            long startTime = System.currentTimeMillis();
            conn.getOutputStream().write(bodyBytes);
            conn.getOutputStream().flush();

            int code = conn.getResponseCode();
            long elapsed = System.currentTimeMillis() - startTime;
            if (code != 200) {
                // H-2：必须消费 errorStream 否则 keep-alive 连接残留 stale socket
                consumeQuietly(conn.getErrorStream());
                LLM_HTTP.debug("[Hermes-Clarify-RESP] HTTP {} ({}ms)，非 200，无 body", code, elapsed);
                LLM_HTTP.debug("[Hermes-Clarify-RESP] ========== 请求结束 ==========");
                log.warn("[HermesBridgeClient] /clarify/{}/answer 返回非 200: {}", clarifyId, code);
                return false;
            }
            // 解析响应中的 resolved 字段
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) sb.append(line);
                String respBody = sb.toString();
                JsonNode resp = objectMapper.readTree(respBody);
                boolean resolved = resp.path("resolved").asBoolean(false);
                // === 响应 DEBUG 日志 ===
                LLM_HTTP.debug("[Hermes-Clarify-RESP] HTTP 200 ({}ms), body 长度={}, resolved={}",
                        elapsed, respBody.length(), resolved);
                LLM_HTTP.debug("[Hermes-Clarify-RESP] Body: {}", respBody);
                LLM_HTTP.debug("[Hermes-Clarify-RESP] ========== 请求结束 ==========");
                log.info("[HermesBridgeClient] clarify 回答已提交 clarify_id={}, resolved={}", clarifyId, resolved);
                return resolved;
            }
        } catch (Exception e) {
            LLM_HTTP.debug("[Hermes-Clarify-RESP] 异常: {}", e.getMessage());
            LLM_HTTP.debug("[Hermes-Clarify-RESP] ========== 请求结束 ==========");
            log.warn("[HermesBridgeClient] 提交 clarify 回答失败 clarify_id={}: {}", clarifyId, e.getMessage());
            return false;
        } finally {
            // H-2：统一在 finally 中 disconnect，无论成功/失败都释放连接
            if (conn != null) {
                try { conn.disconnect(); } catch (Exception ignore) { /* ignore */ }
            }
        }
    }

    /** H-2：静默消费输入流至 EOF，避免 keep-alive 连接残留 stale socket。 */
    private static void consumeQuietly(InputStream is) {
        if (is == null) return;
        try {
            byte[] buf = new byte[1024];
            // 最多读 64KB，避免极端情况下无限读取
            int total = 0;
            int n;
            while (total < 65536 && (n = is.read(buf)) != -1) {
                total += n;
            }
        } catch (IOException ignore) {
            // 静默吞掉，调用方已知道 code != 200
        }
    }

    /**
     * 通知 Bridge 清空技能索引缓存与 Agent 池，使新同步的技能立即生效。
     * 由 HermesSkillSyncService 在技能写时同步后调用；失败仅记录日志，不影响主流程。
     */
    public void invalidateSkillsCache() {
        String url = "http://127.0.0.1:" + properties.getBridge().getPort() + "/skills/invalidate";
        try {
            HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
            conn.setRequestMethod("POST");
            String authToken = properties.getBridge().getAuthToken();
            if (authToken != null && !authToken.isBlank()) {
                conn.setRequestProperty("X-Bridge-Token", authToken);
            }
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(5000);
            int code = conn.getResponseCode();
            if (code != 200) {
                log.warn("[HermesBridgeClient] /skills/invalidate 返回非 200: {}", code);
            } else {
                log.info("[HermesBridgeClient] Bridge 技能缓存已失效");
            }
            conn.disconnect();
        } catch (Exception e) {
            // Bridge 未启动或不可达时静默降级：下次 Bridge 重启后进程级缓存自然重建
            log.warn("[HermesBridgeClient] 调用 /skills/invalidate 失败（Bridge 可能未就绪）: {}", e.getMessage());
        }
    }
}
