package com.agent.management.engine;

import com.agent.management.engine.PlannerService.PlannerDecision;
import com.agent.management.engine.hermes.ClarifyHooks;
import com.agent.management.external.SseEventBus;
import com.agent.management.model.entity.WorkflowRunVisit;
import com.agent.management.repository.WorkflowPauseRepository;
import com.agent.management.repository.WorkflowRunVisitRepository;
import com.agent.management.service.AiModelService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * AGENTIC 模式执行器：与 {@link WorkflowLevelExecutor} 并列，由 {@code WorkflowEngine.execute()}
 * 在 {@code workflow.mode == "AGENTIC"} 时调用。
 *
 * <p>对应设计文档 {@code docs/plans/agentic-workflow-plan.md} §2.1。
 * 核心区别：</p>
 * <ul>
 *   <li>DAG 模式：按画布拓扑严格调度，节点完成 → 沿出边触发后继；节点只执行一次</li>
 *   <li>AGENTIC 模式：单线程顺序执行，每完成一个节点调 {@link PlannerService} 问 LLM 下一步去哪；
 *       节点可被任意次访问，每次访问生成独立 {@link WorkflowRunVisit} 快照</li>
 * </ul>
 *
 * <p>关键不变式（见设计文档 §7）：</p>
 * <ol>
 *   <li>每个 Visit 工作区 = 上一 Visit 工作区 + 本节点输出，同名变量最近覆盖</li>
 *   <li>必须访问到 output 节点才能终结（产出 finalOutputs）</li>
 *   <li>{@link #MAX_VISITS} 硬上限 50，{@link #MAX_SAME_NODE_REVISITS} 同节点连续 5 次兜底</li>
 *   <li>Planner 决策失败必走 {@link PlannerService#fallbackToNextEdge} 兜底</li>
 * </ol>
 */
@Slf4j
@Component
public class AgenticExecutor {

    private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());

    /** 单 run 最多访问 50 次（含 Output 节点） */
    private static final int MAX_VISITS = 50;
    // 注：原 MAX_SAME_NODE_REVISITS=5 已移除。
    // 业务重走（Planner 主动决策的再次访问）是合法行为，不应作为死循环指标。
    // 死循环防护仅靠 MAX_VISITS=50 总量兜底；详见 docs/plans/agentic-revisit-decision-plan.md §4.3

    private final Map<String, NodeExecutor> executorMap;
    private final PlannerService plannerService;
    private final WorkflowRunVisitRepository visitRepo;
    private final NodeWorkspaceBuilder workspaceBuilder;
    private final SseEventBus eventBus;
    private final WorkflowPauseRepository pauseRepository;
    private final AiModelService aiModelService;
    private final ChatClient.Builder fallbackBuilder;

    public AgenticExecutor(Map<String, NodeExecutor> executorMap,
                           PlannerService plannerService,
                           WorkflowRunVisitRepository visitRepo,
                           NodeWorkspaceBuilder workspaceBuilder,
                           SseEventBus eventBus,
                           WorkflowPauseRepository pauseRepository,
                           AiModelService aiModelService,
                           ChatClient.Builder fallbackBuilder) {
        // Spring 注入 Map<String, NodeExecutor> 时 key 是 bean name（如 "userInputExecutor"），
        // 而引擎查找用的是 getType() 返回值（如 "userInput"）。两者命名不一致，
        // 此处按 getType() 重建 map，避免对 bean 命名的依赖。
        // （与 WorkflowLevelExecutor 构造器同款转换，否则会报"找不到节点类型 X 的执行器"）
        Map<String, NodeExecutor> typed = new HashMap<>();
        for (NodeExecutor executor : executorMap.values()) {
            typed.put(executor.getType(), executor);
        }
        this.executorMap = typed;
        this.plannerService = plannerService;
        this.visitRepo = visitRepo;
        this.workspaceBuilder = workspaceBuilder;
        this.eventBus = eventBus;
        this.pauseRepository = pauseRepository;
        this.aiModelService = aiModelService;
        this.fallbackBuilder = fallbackBuilder;
        log.info("[AgenticExecutor] 已注册节点执行器: {}", this.executorMap.keySet());
    }

    /**
     * 入口：与 {@link WorkflowLevelExecutor#executeByLevel} 完全一致的签名，便于 WorkflowEngine 分流。
     *
     * @return ExecutionOutcome（与 DAG 模式同款结构，nodeRecords 为空，由 visitRepo 单独持久化）
     */
    public WorkflowLevelExecutor.ExecutionOutcome execute(DagResolver.ResolvedDag dag,
                                                          WorkflowContext context,
                                                          String runId,
                                                          Long runRecordId,
                                                          SseEmitter emitter) {
        // 注：streamSink 在 while 循环内每轮重新注入，绑定当前 visitUuid，
        // 让 node_stream 事件按 visitUuid 路由（同节点多次访问时 nodeId 会冲突）。

        // 各节点已访问次数（用于 iterSeq 计算 + Planner prompt 提示）
        Map<String, Integer> nodeVisitCounter = new HashMap<>();
        // 当前工作区变量（按 Visit 链累积，同名变量最近覆盖）
        Map<String, Object> currentVars = new LinkedHashMap<>(context.getInitialInputs());

        // 起始节点：userInput 类型优先；否则取入度为 0 的第一个；都没有就取画布第一个节点
        String currentNodeId = findStartNode(dag);
        String fromVisitUuid = null;
        String fromNodeId = null;
        int sortOrder = 0;

        while (sortOrder < MAX_VISITS) {
            // === 0. 预生成本次 Visit 的 UUID（供 nodeRunning / nodeStream / nodeResult 路由使用） ===
            String visitUuid = UUID.randomUUID().toString();

            // === 0.1 累计本节点访问次数（用于 iterSeq 持久化 + Planner prompt 展示，不作为 abort 依据） ===
            // 业务重走（Planner 主动决策的再次访问）是合法行为；死循环防护仅靠 MAX_VISITS=50 总量兜底。
            int revisits = nodeVisitCounter.merge(currentNodeId, 1, Integer::sum);

            // === 1. 重新注入流式回调（每轮循环重建一次，绑定当前 visitUuid） ===
            // 让 node_stream 事件携带正确的 visitUuid，前端按 visitUuid 路由到对应卡片
            final String currentVisitUuid = visitUuid;
            context.setStreamSink(ClarifyHooks.wrap(
                    (nodeId, kind, content, toolName) ->
                            safeSend(emitter, WorkflowRunEvent.nodeStream(
                                    runId, nodeId, currentVisitUuid, kind, content, toolName)),
                    runId,
                    pauseRepository));

            // === 2. 推送 nodeRunning SSE（带 visitUuid） ===
            safeSend(emitter, WorkflowRunEvent.nodeRunning(runId, currentNodeId, visitUuid));

            // === 3. 构建 workspace（AGENTIC 模式：直接用 currentVars，跳过可达前驱计算） ===
            NodeWorkspace ws = buildAgenticWorkspace(currentNodeId, dag, context, currentVars);

            // === 4. 调用 executor（含 maxRetries 重试，默认 3 次；失败用尽后交 Planner） ===
            JsonNode nodeData = dag.getNodeDataMap().get(currentNodeId);
            int maxRetries = Math.max(1, nodeData.path("maxRetries").asInt(3));
            NodeExecutionResult result = null;
            for (int attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    String nodeType = dag.getNodeTypeMap().get(currentNodeId);
                    NodeExecutor executor = executorMap.get(nodeType);
                    if (executor == null) {
                        throw new IllegalStateException("找不到节点类型 " + nodeType + " 的执行器");
                    }
                    result = executor.execute(currentNodeId, nodeData, context, ws);
                } catch (Exception e) {
                    log.error("[Agentic] runId={} 节点 {} 第 {}/{} 次执行异常",
                            runId, currentNodeId, attempt, maxRetries, e);
                    result = NodeExecutionResult.failed(currentNodeId, "节点执行异常: " + e.getMessage());
                }
                // 成功立即跳出；失败则继续重试（用尽后由 Planner 决定下一步）
                if (result.getStatus() == NodeExecutionResult.Status.SUCCESS) {
                    if (attempt > 1) {
                        log.info("[Agentic] runId={} 节点 {} 第 {}/{} 次重试成功",
                                runId, currentNodeId, attempt, maxRetries);
                    }
                    break;
                }
                if (attempt < maxRetries) {
                    log.warn("[Agentic] runId={} 节点 {} 第 {}/{} 次失败，准备重试：{}",
                            runId, currentNodeId, attempt, maxRetries, result.getError());
                }
            }

            // envelope 失败自动升级（与 DAG 模式同款）
            NodeExecutionResult effectiveResult = upgradeIfEnvelopeFailed(result);
            // 把 visitUuid 绑定到 result（nodeResult 推送时携带，前端按 visitUuid 路由）
            effectiveResult = effectiveResult.withVisitUuid(visitUuid);

            // === 4. 失败状态汇总（AGENTIC 模式：不走 failStrategy，统一交 Planner 决定下一步） ===
            boolean lastFailed = effectiveResult.getStatus() == NodeExecutionResult.Status.FAILED;
            String lastError = lastFailed ? effectiveResult.getError() : null;
            if (lastFailed) {
                log.warn("[Agentic] runId={} 节点 {} 重试 {} 次后仍失败，交 Planner 决策：{}",
                        runId, currentNodeId, maxRetries, lastError);
            }

            // === 5. 合并输出到 currentVars（同名变量最近覆盖） ===
            if (effectiveResult.getOutput() != null) {
                Map<String, Object> dataView = extractEnvelopeData(effectiveResult.getOutput());
                if (dataView != null && !dataView.isEmpty()) {
                    // 注入到 currentVars（带 nodeId 前缀，与 NodeWorkspaceBuilder 注入命名空间一致）
                    final String nid = currentNodeId;
                    dataView.forEach((k, v) -> currentVars.put(
                            nid + NodeWorkspaceBuilder.NODE_FIELD_SEPARATOR + k, v));
                    // 同步到 context 命名空间（兼容现有 executor 内部读 scopedOutputs 的逻辑）
                    context.setNodeOutput(currentNodeId, castToMap(effectiveResult.getOutput()));
                }
            }

            // === 6. 持久化 Visit（visitUuid 复用第 0 步预生成的，保证 SSE 事件与 DB 记录一致） ===
            WorkflowRunVisit visit = buildVisit(
                    runRecordId, visitUuid, currentNodeId, revisits,
                    fromVisitUuid, fromNodeId,
                    dag, currentVars, effectiveResult, sortOrder);
            try {
                visitRepo.save(visit);
            } catch (Exception e) {
                log.warn("[Agentic] runId={} visit 持久化失败：{}", runId, e.getMessage());
            }
            // 推送 nodeResult（带 visitUuid）+ visitRecorded：
            // 前端按 visitUuid 路由 nodeResult 到对应 Visit 卡片；visitRecorded 提供卡片元数据
            safeSend(emitter, WorkflowRunEvent.nodeResult(runId, effectiveResult));
            safeSend(emitter, WorkflowRunEvent.visitRecorded(runId, visit));

            // === 7. 终止判定：到达 Output 节点（且成功） ===
            String nodeType = dag.getNodeTypeMap().get(currentNodeId);
            if ("output".equals(nodeType)
                    && effectiveResult.getStatus() == NodeExecutionResult.Status.SUCCESS) {
                return complete(context, emitter, runId, effectiveResult.getOutput(),
                        sortOrder + 1, visitUuid, currentNodeId);
            }

            // === 8. 调用 Planner 决定下一个节点（lastFailed 时 prompt 会带上失败原因；
            //    lastNodeOutput 让 Planner 直接看到节点 final_response 中的"用户新需求"等关键信号） ===
            PlannerDecision decision;
            try {
                decision = plannerService.decideNext(
                        dag, currentNodeId, currentVars, nodeVisitCounter,
                        aiModelService, fallbackBuilder, lastFailed, lastError,
                        effectiveResult.getOutput());
            } catch (Exception e) {
                log.warn("[Agentic] runId={} Planner 失败，走兜底：{}", runId, e.getMessage());
                decision = PlannerService.fallbackToNextEdge(dag, currentNodeId);
            }

            // 校验 next 节点合法（不在画布上 → 再次兜底）
            if (!dag.getNodeDataMap().containsKey(decision.getNextNodeId())) {
                log.warn("[Agentic] runId={} Planner 返回非法节点 {}，走兜底",
                        runId, decision.getNextNodeId());
                decision = PlannerService.fallbackToNextEdge(dag, currentNodeId);
            }

            // 推送决策事件（前端用于展示"AI 决策链"）
            safeSend(emitter, WorkflowRunEvent.plannerDecision(runId, decision));

            // === 9. 准备下一次迭代 ===
            fromNodeId = currentNodeId;
            fromVisitUuid = visitUuid;
            currentNodeId = decision.getNextNodeId();
            sortOrder++;
        }

        // === 10. maxVisits 用尽 ===
        String msg = "超过最大访问次数 " + MAX_VISITS + "，疑似死循环";
        log.warn("[Agentic] runId={} {}", runId, msg);
        return abort(context, emitter, runId, msg,
                sortOrder, null, fromVisitUuid, fromNodeId, currentVars);
    }

    /* ============== 终止辅助 ============== */

    private WorkflowLevelExecutor.ExecutionOutcome complete(WorkflowContext context, SseEmitter emitter,
                                                             String runId, Map<String, Object> finalOutput,
                                                             int visitCount,
                                                             String lastVisitUuid, String lastNodeId) {
        context.setStreamSink(null);
        Map<String, Object> outputs = finalOutput != null ? finalOutput : new LinkedHashMap<>();
        safeSend(emitter, WorkflowRunEvent.workflowComplete(runId, outputs, List.of()));
        log.info("[Agentic] runId={} 完成，共 {} 次访问，最后节点 {}",
                runId, visitCount, lastNodeId);
        return new WorkflowLevelExecutor.ExecutionOutcome(outputs, Collections.emptyList(), false, null);
    }

    private WorkflowLevelExecutor.ExecutionOutcome abort(WorkflowContext context, SseEmitter emitter,
                                                          String runId, String msg,
                                                          int visitCount,
                                                          String currentNodeId,
                                                          String fromVisitUuid, String fromNodeId,
                                                          Map<String, Object> currentVars) {
        context.setStreamSink(null);
        safeSend(emitter, WorkflowRunEvent.workflowError(runId, msg));
        log.warn("[Agentic] runId={} abort: {}", runId, msg);
        return new WorkflowLevelExecutor.ExecutionOutcome(
                new LinkedHashMap<>(), Collections.emptyList(), true, msg);
    }

    /* ============== 私有：节点查找 + workspace 构建 ============== */

    /** 起始节点优先级：userInput 类型 → 入度为 0 → 画布第一个节点 */
    private String findStartNode(DagResolver.ResolvedDag dag) {
        // 1. userInput 类型优先
        for (Map.Entry<String, String> e : dag.getNodeTypeMap().entrySet()) {
            if ("userInput".equals(e.getValue())) return e.getKey();
        }
        // 2. 入度为 0 的节点
        for (Map.Entry<String, List<DagResolver.EdgeInfo>> e : dag.getIncomingEdges().entrySet()) {
            if (e.getValue() == null || e.getValue().isEmpty()) return e.getKey();
        }
        // 3. 兜底：画布第一个节点（按 levels 顺序）
        for (List<String> level : dag.getLevels()) {
            if (!level.isEmpty()) return level.get(0);
        }
        throw new IllegalStateException("画布上无任何节点");
    }

    /**
     * 构建 AGENTIC 模式 workspace：跳过可达前驱计算，直接用 currentVars 作为变量来源。
     * <p>同时填充 scopedOutputs（按 nodeId 拆分 currentVars 中带 "__" 前缀的变量），
     * 让 executor 内部读 scopedOutputs 的逻辑（如 LlmExecutor 取上下文）正常工作。</p>
     */
    private NodeWorkspace buildAgenticWorkspace(String nodeId, DagResolver.ResolvedDag dag,
                                                 WorkflowContext context,
                                                 Map<String, Object> currentVars) {
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId(context.getRunId());
        ws.getVariables().putAll(currentVars);
        // AGENTIC 模式暂不扫描工作目录文件，保持与原 scanWorkingDirSafe 行为一致
        ws.setFiles(List.of());

        // 把 currentVars 中带 "__" 前缀的变量按 nodeId 分组，填入 scopedOutputs
        Map<String, Map<String, Object>> grouped = new LinkedHashMap<>();
        for (Map.Entry<String, Object> e : currentVars.entrySet()) {
            int sep = e.getKey().indexOf(NodeWorkspaceBuilder.NODE_FIELD_SEPARATOR);
            if (sep > 0) {
                String fromNode = e.getKey().substring(0, sep);
                String varName = e.getKey().substring(sep + NodeWorkspaceBuilder.NODE_FIELD_SEPARATOR.length());
                grouped.computeIfAbsent(fromNode, k -> new LinkedHashMap<>()).put(varName, e.getValue());
            }
        }
        ws.getScopedOutputs().putAll(grouped);
        return ws;
    }

    /* ============== 私有：Visit 构建 + envelope 处理 ============== */

    private WorkflowRunVisit buildVisit(Long runRecordId, String visitUuid, String nodeId, int iterSeq,
                                         String fromVisitUuid, String fromNodeId,
                                         DagResolver.ResolvedDag dag,
                                         Map<String, Object> currentVars,
                                         NodeExecutionResult result, int sortOrder) {
        WorkflowRunVisit v = new WorkflowRunVisit();
        v.setRunId(runRecordId);
        v.setVisitUuid(visitUuid);
        v.setNodeId(nodeId);
        v.setNodeType(dag.getNodeTypeMap().get(nodeId));
        JsonNode data = dag.getNodeDataMap().get(nodeId);
        v.setNodeLabel(data != null ? data.path("label").asText(nodeId) : nodeId);
        v.setIterSeq(iterSeq);
        v.setFromVisitUuid(fromVisitUuid);
        v.setFromNodeId(fromNodeId);
        try {
            v.setVariablesJson(MAPPER.writeValueAsString(currentVars));
        } catch (Exception e) {
            v.setVariablesJson("{}");
        }
        try {
            v.setOutputJson(result.getOutput() != null ? MAPPER.writeValueAsString(result.getOutput()) : null);
        } catch (Exception e) {
            v.setOutputJson(null);
        }
        try {
            // logsJson：让运行记录页能从 Visit 还原 streamingThinking / toolHistory / todos
            if (result.getLogs() != null && !result.getLogs().isEmpty()) {
                v.setLogsJson(MAPPER.writeValueAsString(result.getLogs()));
            }
        } catch (Exception e) {
            v.setLogsJson(null);
        }
        // decisionJson：仅记录"非画布跳转"标志（fromNodeId→nodeId 不在画布 outgoing edges 中）。
        // 前端据此决定是否展示"来自 X"徽章：null/无字段=按画布跳转（与 DAG 一致不展示），有字段=自由跳转。
        // 起始节点（fromNodeId=null）也写入 null。
        v.setDecisionJson(buildDecisionJson(dag, fromNodeId, nodeId));
        v.setStatus(result.getStatus().name());
        v.setErrorMsg(result.getError());
        v.setSortOrder(sortOrder);
        return v;
    }

    /**
     * 计算 visit 跳转元数据 JSON。
     * <p>仅记录"非画布跳转"标志：</p>
     * <ul>
     *   <li>起始节点（fromNodeId=null）→ null（无前驱，不需要标识）</li>
     *   <li>fromNodeId→nodeId 在画布 outgoing edges 中 → null（按画布走，与 DAG 一致）</li>
     *   <li>fromNodeId→nodeId 不在画布 outgoing edges 中 → {@code {"offCanvas":true}}（自由跳转）</li>
     * </ul>
     */
    private String buildDecisionJson(DagResolver.ResolvedDag dag, String fromNodeId, String nodeId) {
        if (fromNodeId == null || nodeId == null) return null;
        List<DagResolver.EdgeInfo> outEdges = dag.getOutgoingEdges().get(fromNodeId);
        boolean onCanvas = outEdges != null
                && outEdges.stream().anyMatch(e -> nodeId.equals(e.getTarget()));
        return onCanvas ? null : "{\"offCanvas\":true}";
    }

    /** envelope 失败自动升级（与 DAG 模式 onNodeCompleted 中的 D 项一致） */
    private NodeExecutionResult upgradeIfEnvelopeFailed(NodeExecutionResult result) {
        if (result.getStatus() == NodeExecutionResult.Status.SUCCESS
                && result.getOutput() != null
                && NodeOutputEnvelope.isEnvelope(result.getOutput())) {
            NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(result.getOutput());
            if (env != null && env.isFailure()) {
                String msg = env.getMessage();
                return NodeExecutionResult.failed(result.getNodeId(),
                        "节点业务执行失败（envelope.status=400）: "
                                + (msg == null || msg.isEmpty() ? "未提供失败原因" : msg),
                        result.getLogs());
            }
        }
        return result;
    }

    /** 从 envelope 输出中提取 data 字段；非 envelope 则原样返回 */
    private Map<String, Object> extractEnvelopeData(Map<String, Object> output) {
        if (output == null) return Map.of();
        if (NodeOutputEnvelope.isEnvelope(output)) {
            NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(output);
            return env != null && env.getData() != null ? env.getData() : Map.of();
        }
        return output;
    }

    /* ============== SSE 安全推送（与 WorkflowLevelExecutor.safeSend 同款逻辑） ============== */

    private synchronized void safeSend(SseEmitter emitter, WorkflowRunEvent event) {
        eventBus.publish(event.getRunId(), event.getType(), event);
        try {
            emitter.send(SseEmitter.event()
                    .name(event.getType())
                    .data(event));
        } catch (IOException e) {
            log.warn("[Agentic] SSE 推送失败: {}", e.getMessage());
        }
    }

    /** 抑制 unchecked 警告：executor 返回 Map<String,Object> 已知类型 */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> castToMap(Object o) {
        return o instanceof Map ? (Map<String, Object>) o : Map.of();
    }
}
