package com.agent.management.service;

import com.agent.management.common.exception.BusinessException;
import com.agent.management.model.entity.Workflow;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 工作流描述智能生成服务。
 *
 * <p>根据工作流当前描述 + 节点配置，调用 LLM 先判断当前描述是否已说清楚
 * 「作用 + 使用场景」，不清楚时才生成新描述。本服务只返回结果、不保存，
 * 由调用方决定是否写回（编辑器场景不自动保存；列表批量场景由前端再调 save）。
 *
 * <p>描述撰写要求参考 skill-creator 技能的最佳实践：
 * 聚焦「何时使用」的触发场景、祈使句、100-200 字。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkflowDescriptionService {

    private final WorkflowService workflowService;
    private final AiModelService aiModelService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /** 描述硬上限：与前端输入框 maxlength=500 对齐 */
    private static final int DESC_MAX_LEN = 500;

    /** 节点 data 中不参与摘要的 UI/噪音字段 */
    private static final Set<String> SKIP_KEYS = Set.of(
            "label", "position", "selected", "dragging", "measured",
            "width", "height", "id", "type", "virtual");

    /**
     * 判断/生成工作流描述。
     *
     * @param nameOrId 工作流 name（kebab-case），旧数字 id 可兜底解析
     * @return skipped=true 表示当前描述已清晰无需修改；否则 description 为新生成的描述
     */
    public GenerateResult generateDescription(String nameOrId) {
        long start = System.currentTimeMillis();
        Workflow wf = workflowService.getWorkflow(nameOrId);
        String nodesSummary = summarizeGraph(wf.getGraphData());
        String prompt = buildPrompt(wf, nodesSummary);
        log.info("[GenDesc] 开始生成: workflow={}（{}）, 节点摘要长度={}, prompt长度={}",
                wf.getName(), wf.getDisplayName(), nodesSummary.length(), prompt.length());
        log.debug("[GenDesc] prompt 全文:\n{}", prompt);

        String llmOutput;
        try {
            llmOutput = aiModelService.systemChatCall(client ->
                    client.prompt().user(prompt).call().content());
        } catch (Exception e) {
            log.warn("[GenDesc] LLM 调用失败: workflow={}, 耗时={}ms, error={}",
                    wf.getName(), System.currentTimeMillis() - start, e.getMessage());
            throw new BusinessException("生成描述失败: " + e.getMessage());
        }
        log.info("[GenDesc] LLM 返回: workflow={}, 耗时={}ms, 原始输出=\n{}",
                wf.getName(), System.currentTimeMillis() - start, llmOutput);

        GenerateResult result = parseResult(llmOutput);
        log.info("[GenDesc] 解析结果: workflow={}, skipped={}, {}",
                wf.getName(), result.skipped(),
                result.skipped() ? "reason=" + result.reason() : "description=" + result.description());
        return result;
    }

    /** 构建单次调用 prompt：先判断再按需生成，JSON 结构化输出 */
    private String buildPrompt(Workflow wf, String nodesSummary) {
        String current = wf.getDescription() == null ? "" : wf.getDescription().trim();
        String display = wf.getDisplayName() == null ? "" : wf.getDisplayName();
        StringBuilder sb = new StringBuilder();
        sb.append("你是智能体工作流的描述撰写专家。业务应用系统会阅读工作流的描述，来决定在什么场景下自动调用它。\n\n");
        sb.append("【工作流】").append(display).append("（").append(wf.getName()).append("）\n");
        sb.append("【当前描述】\n").append(current.isEmpty() ? "（空）" : current).append("\n\n");
        sb.append("【节点配置】\n").append(nodesSummary).append('\n');
        sb.append("请完成两个任务：\n");
        sb.append("1. 判断「当前描述」是否已经清楚地说明了该工作流的作用和使用场景（即在什么情况下应该调用它）。\n");
        sb.append("   描述为空、过于笼统、只说了功能没说使用场景，都算「不清楚」。\n");
        sb.append("2. 只输出 JSON（不要输出任何其他内容、不要用 markdown 代码块）：\n");
        sb.append("   - 若已清楚：{\"clear\": true}\n");
        sb.append("   - 若不清楚：{\"clear\": false, \"description\": \"新生成的描述\"}\n\n");
        sb.append("新描述撰写要求：\n");
        sb.append("- 100-200 字以内，同时覆盖「做什么」和「何时应该调用它」\n");
        sb.append("- 聚焦业务用途与触发场景，不要罗列技术实现细节\n");
        sb.append("- 使用「当用户需要……时使用本工作流」的句式\n");
        return sb.toString();
    }

    /** 把 graphData 压缩成 LLM 可读的节点配置摘要（剔除 UI 字段，长值截断） */
    private String summarizeGraph(String graphData) {
        if (graphData == null || graphData.isBlank()) return "（空工作流）";
        try {
            JsonNode root = MAPPER.readTree(graphData);
            StringBuilder sb = new StringBuilder();
            JsonNode nodes = root.get("nodes");
            if (nodes != null && nodes.isArray()) {
                for (JsonNode node : nodes) {
                    String id = node.path("id").asText("?");
                    String type = node.path("type").asText("?");
                    JsonNode data = node.get("data");
                    String label = data != null ? data.path("label").asText("") : "";
                    sb.append("- 节点「").append(label.isEmpty() ? id : label)
                            .append("」（").append(type).append("）\n");
                    if (data != null && data.isObject()) {
                        Iterator<Map.Entry<String, JsonNode>> fields = data.fields();
                        while (fields.hasNext()) {
                            Map.Entry<String, JsonNode> entry = fields.next();
                            String key = entry.getKey();
                            if (SKIP_KEYS.contains(key)) continue;
                            String val = stringify(entry.getValue());
                            if (val == null || val.isBlank()) continue;
                            sb.append("    ").append(key).append(": ")
                                    .append(truncate(val, 300)).append('\n');
                        }
                    }
                }
            }
            JsonNode edges = root.get("edges");
            if (edges != null && edges.isArray() && !edges.isEmpty()) {
                sb.append("连线：");
                for (JsonNode e : edges) {
                    sb.append(e.path("source").asText("?")).append("→")
                            .append(e.path("target").asText("?")).append("  ");
                }
                sb.append('\n');
            }
            return sb.toString();
        } catch (Exception e) {
            log.warn("[GenDesc] graphData 解析失败: {}", e.getMessage());
            return "（配置解析失败）";
        }
    }

    private String stringify(JsonNode v) {
        if (v == null || v.isNull()) return null;
        if (v.isTextual() || v.isNumber() || v.isBoolean()) return v.asText();
        return v.toString();
    }

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

    /** 解析 LLM 返回：三级降级提取 JSON（与 SkillGenerationController.parseRefineResult 同策略） */
    private GenerateResult parseResult(String llmOutput) {
        if (llmOutput == null || llmOutput.isBlank()) {
            throw new BusinessException("AI 未返回内容，请重试");
        }
        String json = extractJson(llmOutput);
        if (json == null) {
            throw new BusinessException("AI 返回格式异常，请重试");
        }
        try {
            JsonNode node = MAPPER.readTree(json);
            boolean clear = node.path("clear").asBoolean(false);
            if (clear) {
                return new GenerateResult(true, "当前描述已清晰说明作用与使用场景，无需修改", null);
            }
            String desc = node.path("description").asText("").trim();
            if (desc.isEmpty()) {
                throw new BusinessException("AI 未生成有效描述，请重试");
            }
            if (desc.length() > DESC_MAX_LEN) {
                desc = desc.substring(0, DESC_MAX_LEN);
            }
            return new GenerateResult(false, null, desc);
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            throw new BusinessException("AI 返回解析失败: " + e.getMessage());
        }
    }

    /** 从可能包含 markdown 代码块或额外文字的文本中提取 JSON 对象 */
    private String extractJson(String text) {
        String trimmed = text.trim();
        if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
        int fenceStart = trimmed.indexOf("```");
        if (fenceStart >= 0) {
            int contentStart = trimmed.indexOf('\n', fenceStart);
            if (contentStart > 0) {
                int contentEnd = trimmed.indexOf("```", contentStart + 1);
                if (contentEnd > contentStart) {
                    String inner = trimmed.substring(contentStart + 1, contentEnd).trim();
                    if (inner.startsWith("{") && inner.endsWith("}")) return inner;
                }
            }
        }
        int firstBrace = trimmed.indexOf('{');
        int lastBrace = trimmed.lastIndexOf('}');
        if (firstBrace >= 0 && lastBrace > firstBrace) {
            return trimmed.substring(firstBrace, lastBrace + 1);
        }
        return null;
    }

    /**
     * 生成结果。
     *
     * @param skipped     true 表示当前描述已清晰无需修改
     * @param reason      skipped 时的说明文案
     * @param description 新生成的描述（skipped=false 时非空）
     */
    public record GenerateResult(boolean skipped, String reason, String description) {
    }
}
