package com.agent.management.engine;

import com.agent.management.engine.executor.OutputExecutor;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * {@link OutputExecutor} 单元测试。
 *
 * <p>验证输出节点按 {@code data.fields} 投影的新语义：</p>
 * <ul>
 *   <li>未声明 fields → data 为空（严格语义，_runId 不再注入 envelope）</li>
 *   <li>声明 fields → 用 field.name 作为裸 key 输出，复用 NodeInputResolver 的 mapping + 四级模糊匹配 + 类型转换</li>
 *   <li>required=true 缺失 → 失败 envelope</li>
 *   <li>required=false 缺失 → 字段不出现</li>
 * </ul>
 */
class OutputExecutorTest {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static JsonNode data(String json) {
        try {
            return MAPPER.readTree(json);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** 构造一个 envelope 形式的前驱输出（与 NodeWorkspaceBuilder 注入 scopedOutputs 的形态一致）。 */
    private static Map<String, Object> envelopeWith(String varName, Object value) {
        return NodeOutputEnvelope.success("ok", Map.of(varName, value)).toMap();
    }

    private static int envelopeStatus(NodeExecutionResult result) {
        Object raw = result.getOutput().get("status");
        if (raw instanceof Number) return ((Number) raw).intValue();
        if (raw instanceof String) return Integer.parseInt((String) raw);
        throw new AssertionError("缺失 status 字段：" + result.getOutput());
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> envelopeData(NodeExecutionResult result) {
        return (Map<String, Object>) result.getOutput().get("data");
    }

    private static String envelopeMessage(NodeExecutionResult result) {
        Object msg = result.getOutput().get("message");
        return msg == null ? "" : msg.toString();
    }

    /** 构造一个工作区，其扁平变量表与 scopedOutputs 已对齐 NodeWorkspaceBuilder 的注入约定。 */
    private static NodeWorkspace workspaceWith(String predId, String varName, Object value) {
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId("run-test");
        Map<String, Object> env = envelopeWith(varName, value);
        ws.getScopedOutputs().put(predId, env);
        // 扁平变量按 NodeWorkspaceBuilder 的命名空间约定：predId + "__" + varName
        ws.getVariables().put(predId + NodeWorkspaceBuilder.NODE_FIELD_SEPARATOR + varName, value);
        return ws;
    }

    @Test
    void noFieldsDeclReturnsOnlyMetadata() {
        // 未声明 fields：data 为空（严格语义，_runId 已改由 WorkflowRun 持有，不再注入 envelope）
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());

        NodeExecutionResult result = executor.execute("out-1", data("{}"), ctx, null);

        assertThat(result.getStatus()).isEqualTo(NodeExecutionResult.Status.SUCCESS);
        assertThat(envelopeStatus(result)).isEqualTo(200);
        Map<String, Object> d = envelopeData(result);
        assertThat(d).isEmpty();
    }

    @Test
    void fieldWithExplicitMappingUsesBareKey() {
        // field.name=result，mapping 指向 node-1.result → 输出 key 用裸名 result（不带 predId__ 前缀）
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-1", "result", "来自前驱的值");

        JsonNode data = data("{\"fields\":[{\"name\":\"result\",\"type\":\"string\",\"required\":true," +
                "\"mapping\":{\"sourceNodeId\":\"node-1\",\"sourceField\":\"result\"}}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        Map<String, Object> d = envelopeData(result);
        assertThat(d).containsEntry("result", "来自前驱的值");
        assertThat(d).doesNotContainKey("node-1__result");
    }

    @Test
    void fieldWithoutMappingAutoInfersByExactName() {
        // 无 mapping：NodeInputResolver 按精确同名从 scopedOutputs 取值
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-1", "result", "hello");

        JsonNode data = data("{\"fields\":[{\"name\":\"result\",\"type\":\"string\",\"required\":true}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        assertThat(envelopeData(result)).containsEntry("result", "hello");
    }

    @Test
    void hyphenFieldNameMatchesUnderscoreSource() {
        // 用户场景：fields 声明 git-commit-log（连字符），前驱输出 git_commit_log（下划线）
        // NodeInputResolver 的"忽略 -_/大小写同名"分支应命中
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-2", "git_commit_log", "提交日志内容");

        JsonNode data = data("{\"fields\":[{\"name\":\"git-commit-log\",\"type\":\"string\",\"required\":true}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        // 输出 key 沿用用户声明的 field.name（git-commit-log）
        assertThat(envelopeData(result)).containsEntry("git-commit-log", "提交日志内容");
    }

    @Test
    void requiredFieldMissingReturnsFailureEnvelope() {
        // required=true 字段在前驱中不存在 → 返回 FAILED envelope
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-1", "other", "value");

        JsonNode data = data("{\"fields\":[{\"name\":\"missing\",\"type\":\"string\",\"required\":true}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(400);
        assertThat(envelopeMessage(result)).contains("missing");
    }

    @Test
    void requiredFalseFieldMissingOmittedFromOutput() {
        // required=false 字段缺失：不出现于输出，但不影响 envelope 状态
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId("run-1");

        JsonNode data = data("{\"fields\":[{\"name\":\"optional\",\"type\":\"string\",\"required\":false}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        Map<String, Object> d = envelopeData(result);
        assertThat(d).doesNotContainKey("optional");
        // optional 字段缺失时不进入输出，data 保持为空（_runId 不再注入）
        assertThat(d).isEmpty();
    }

    @Test
    void legacyFieldWithoutRequiredFlagDefaultsToOptional() {
        // 历史 fields 项未带 required：默认非必填（与 NodeInputResolver 的 asBoolean(false) 一致）
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId("run-1");

        JsonNode data = data("{\"fields\":[{\"name\":\"legacy\",\"type\":\"string\"}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        assertThat(envelopeData(result)).doesNotContainKey("legacy");
    }

    @Test
    void multipleFieldsAllResolved() {
        // 多字段场景：result 来自前驱，summary 也来自前驱，按各自 field.name 输出
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId("run-1");
        // 构造一个含两个字段的 envelope
        Map<String, Object> envData = new LinkedHashMap<>();
        envData.put("result", "成功");
        envData.put("summary", "汇总");
        Map<String, Object> env = NodeOutputEnvelope.success("ok", envData).toMap();
        ws.getScopedOutputs().put("node-1", env);
        ws.getVariables().put("node-1__result", "成功");
        ws.getVariables().put("node-1__summary", "汇总");

        JsonNode data = data("{\"fields\":[" +
                "{\"name\":\"result\",\"type\":\"string\",\"required\":true}," +
                "{\"name\":\"summary\",\"type\":\"string\",\"required\":true}" +
                "]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        Map<String, Object> d = envelopeData(result);
        assertThat(d).containsEntry("result", "成功").containsEntry("summary", "汇总");
    }

    @Test
    void multipleRequiredMissingAllReported() {
        // 多个 required 字段同时缺失 → 失败信息应列出全部缺失字段名
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-1", "present", "v");

        JsonNode data = data("{\"fields\":[" +
                "{\"name\":\"present\",\"type\":\"string\",\"required\":true}," +
                "{\"name\":\"missing1\",\"type\":\"string\",\"required\":true}," +
                "{\"name\":\"missing2\",\"type\":\"string\",\"required\":true}" +
                "]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(400);
        String msg = envelopeMessage(result);
        assertThat(msg).contains("missing1").contains("missing2").doesNotContain("present");
    }

    @Test
    void initialInputInjectedAsBareKey() {
        // 用户运行对话框输入（initialInputs）以扁平形式注入 workspace.variables
        // 字段同名时 NodeInputResolver 走第 2 步直接命中，输出 key 用 field.name
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = new NodeWorkspace();
        ws.setRunId("run-1");
        ws.getVariables().put("userInput", "hello");

        JsonNode data = data("{\"fields\":[{\"name\":\"userInput\",\"type\":\"string\",\"required\":true}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        assertThat(envelopeStatus(result)).isEqualTo(200);
        assertThat(envelopeData(result)).containsEntry("userInput", "hello");
    }

    @Test
    void emptyStringTreatedAsMissingForRequired() {
        // NodeInputResolver 第 2 步用 null 判定（非空字符串判定）；
        // 此处构造 required 字段在前驱完全不存在 → 触发 missing
        OutputExecutor executor = new OutputExecutor();
        WorkflowContext ctx = new WorkflowContext(1L, "run-1", null, Map.of());
        NodeWorkspace ws = workspaceWith("node-1", "foo", "");

        JsonNode data = data("{\"fields\":[{\"name\":\"foo\",\"type\":\"string\",\"required\":true}]}");

        NodeExecutionResult result = executor.execute("out-1", data, ctx, ws);

        // 空字符串：NodeInputResolver 仍会作为 value 返回（非 null），因此不进入 missing
        // 这与原 OutputExecutor 的"空字符串视为缺失"行为不同，但与所有其他节点（llm/agent/skill）保持一致
        assertThat(envelopeStatus(result)).isEqualTo(200);
        assertThat(envelopeData(result)).containsEntry("foo", "");
    }
}
