package com.agent.management.engine.executor;

import com.agent.management.engine.*;
import com.agent.management.config.WorkflowProperties;
import com.agent.management.model.dto.IOField;
import com.agent.management.model.dto.SkillDTO;
import com.agent.management.service.AiModelService;
import com.agent.management.service.SkillService;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Agent 节点执行器（内置 LLM 模式）
 * 读取关联 Skill 的 SKILL.md 内容作为系统提示，结合 Skill 的 inputs 构造用户消息
 * 支持 modelId 字段指定模型
 *
 * 当 hermes.enabled=true 时此类不激活，由 HermesAgentExecutor 替代。
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "hermes.enabled", havingValue = "false", matchIfMissing = true)
public class AgentExecutor implements NodeExecutor {

    private final AiModelService aiModelService;
    private final ChatClient.Builder fallbackBuilder;
    private final SkillService skillService;
    private final WorkflowProperties workflowProperties;

    public AgentExecutor(ChatClient.Builder chatClientBuilder, AiModelService aiModelService,
                         SkillService skillService, WorkflowProperties workflowProperties) {
        this.fallbackBuilder = chatClientBuilder;
        this.aiModelService = aiModelService;
        this.skillService = skillService;
        this.workflowProperties = workflowProperties;
    }

    @Override
    public String getType() {
        return "agent";
    }

    @Override
    public NodeExecutionResult execute(String nodeId, JsonNode data, WorkflowContext context, NodeWorkspace workspace) {
        String agentId = data.path("agentId").asText("");
        if (agentId.isEmpty()) {
            return NodeExecutionResult.success(nodeId,
                    NodeOutputEnvelope.failure("Agent 节点未关联 Skill").toMap());
        }

        // 加载 Skill 元数据和完整内容
        SkillDTO skill;
        String skillContent;
        try {
            skill = skillService.getSkill(agentId);
            skillContent = skillService.getSkillFullContent(agentId);
        } catch (Exception e) {
            return NodeExecutionResult.success(nodeId,
                    NodeOutputEnvelope.failure("加载 Skill 失败: " + e.getMessage()).toMap());
        }

        // 构造用户消息：基于 Skill 的 inputs 定义从工作区取值
        String userMessage = buildUserMessage(skill, workspace);
        ChatClient client = resolveChatClient(data);
        JsonNode outputsDecl = data.path("outputs");
        boolean structured = StructuredOutputHelper.needsStructuredOutput(data);

        log.info("[Agent] 节点 {} 执行 Skill: {}, 用户消息长度={}, structured={}",
                nodeId, agentId, userMessage.length(), structured);

        // 结构化模式：走与 LLM 节点一致的 extractWithRetry 流程
        if (structured) {
            // 把节点明确定义的输入变量追加到 SKILL.md 内容之后，作为完整系统提示
            String fullSystem = ContextPromptHelper.merge(skillContent, data, workspace);
            RetryPolicy policy = RetryPolicy.fromNodeData(data, workflowProperties);
            StructuredOutputHelper.ExtractionResult ext =
                    StructuredOutputHelper.extractWithRetry(client, userMessage, fullSystem, outputsDecl, policy);

            if (ext.errorMessage != null) {
                log.warn("[Agent] 节点 {} 结构化输出失败（重试 {} 次）: {}",
                        nodeId, ext.attempts, ext.errorMessage);
                return NodeExecutionResult.success(nodeId,
                        NodeOutputEnvelope.failure(ext.errorMessage).toMap());
            }

            String msg = "Agent 执行成功" + (ext.attempts > 1 ? "（重试 " + (ext.attempts - 1) + " 次）" : "");
            log.info("[Agent] 节点 {} 完成, 输出字段数={}, attempts={}", nodeId, ext.data.size(), ext.attempts);
            return NodeExecutionResult.success(nodeId,
                    NodeOutputEnvelope.success(msg, ext.data).toMap());
        }

        // 单输出降级模式：未声明 outputs，按默认变量名写一个字段
        try {
            // 把节点明确定义的输入变量追加到 SKILL.md 内容之后，作为完整系统提示
            String fullSystem = ContextPromptHelper.merge(skillContent, data, workspace);
            var request = client.prompt()
                    .system(fullSystem)
                    .user(userMessage);
            String result = request.call().content();

            if (result == null || result.isBlank()) {
                return NodeExecutionResult.success(nodeId,
                        NodeOutputEnvelope.failure("Agent 返回空结果").toMap());
            }

            log.info("[Agent] 节点 {} 完成, 结果长度={}", nodeId, result.length());
            String varName = NodeTypeUtils.resolveOutputVarName(data, "result");
            Map<String, Object> dataMap = new LinkedHashMap<>();
            dataMap.put(varName, result);
            return NodeExecutionResult.success(nodeId,
                    NodeOutputEnvelope.success("Agent 执行成功", dataMap).toMap());

        } catch (Exception e) {
            log.error("[Agent] 节点 {} 调用失败: {}", nodeId, e.getMessage());
            return NodeExecutionResult.success(nodeId,
                    NodeOutputEnvelope.failure("Agent 调用失败: " + e.getMessage()).toMap());
        }
    }

    private ChatClient resolveChatClient(JsonNode data) {
        long modelId = data.path("modelId").asLong(0);
        if (modelId > 0) {
            try {
                return aiModelService.getChatClient(modelId);
            } catch (Exception e) {
                log.warn("[Agent] 指定模型 {} 获取失败，回退到默认模型: {}", modelId, e.getMessage());
            }
        }
        // 每次动态获取默认模型，确保模型配置变更能及时生效
        return aiModelService.getChatClientOrFallback(fallbackBuilder);
    }

    /**
     * 根据 Skill 的 inputs 定义，从工作区构造结构化用户消息
     */
    private String buildUserMessage(SkillDTO skill, NodeWorkspace workspace) {
        StringBuilder sb = new StringBuilder();

        if (skill.getInputs() != null && !skill.getInputs().isEmpty()) {
            for (IOField input : skill.getInputs()) {
                Object value = workspace.getVariable(input.getName());
                if (value != null) {
                    sb.append(input.getName()).append(": ").append(value).append("\n");
                } else {
                    log.debug("[Agent] Skill 输入字段 {} 在工作区中未找到值", input.getName());
                }
            }
        }

        if (sb.isEmpty() && !workspace.getVariables().isEmpty()) {
            sb.append(workspace.getVariables().entrySet().stream()
                    .filter(e -> e.getValue() != null)
                    .map(e -> e.getKey() + ": " + e.getValue())
                    .collect(Collectors.joining("\n")));
        }

        return sb.toString();
    }
}
