package com.agent.management.engine;

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.List;
import java.util.Map;

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

/**
 * {@link OutputValidator} strict 模式（{@code validate(parsed, decl, true)}）单元测试。
 *
 * <p>strict 模式规则（修订后）：</p>
 * <ul>
 *   <li>只校验 {@code required=true} 字段：必须精确存在（区分大小写、区分分隔符）且值非空</li>
 *   <li>非 required 字段：缺失不报错；存在则做类型校验</li>
 *   <li>多余字段（含 LLM 自由扩展的 snake_case 辅助字段）完全不报错</li>
 * </ul>
 *
 * <p>测试辅助方法 {@link #outputsDecl(String...)} 默认所有字段 required=true，
 * 显式标注 "optional" 才是非必填。</p>
 */
class OutputValidatorStrictTest {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static JsonNode outputsDecl(String... fields) {
        // fields 形如 "name|type" 或 "name|type|required" 或 "name|type|optional"
        // 默认 required=true（匹配修订后 strict 模式的常用场景）
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < fields.length; i++) {
            if (i > 0) sb.append(",");
            String[] parts = fields[i].split("\\|");
            sb.append("{\"name\":\"").append(parts[0]).append("\",")
              .append("\"type\":\"").append(parts[1]).append("\",")
              .append("\"required\":")
              .append(parts.length > 2 && "optional".equalsIgnoreCase(parts[2]) ? "false" : "true");
            sb.append("}");
        }
        sb.append("]");
        try {
            return MAPPER.readTree(sb.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    void strictMode_allFieldsPresent_ok() {
        JsonNode decl = outputsDecl("status|number", "message|string", "data|object");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        parsed.put("message", "ok");
        parsed.put("data", Map.of("fieldA", "value"));

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
        assertThat(result.keyNameMismatched).isEmpty();
        assertThat(result.missingRequired).isEmpty();
        assertThat(result.typeMismatched).isEmpty();
    }

    @Test
    void strictMode_chineseKeyNameRejected() {
        // 用户定义：英文键名；LLM 返回：中文键名 → strict 模式应拒绝
        JsonNode decl = outputsDecl("status|number", "message|string", "data|object");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("状态", 200);
        parsed.put("信息", "成功");
        parsed.put("数据", Map.of("字段A", "值"));

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        // 三个声明的英文字段都未在 parsed 中精确存在
        assertThat(result.keyNameMismatched).containsExactlyInAnyOrder("status", "message", "data");
        // 错误信息应列出所有缺失键名
        assertThat(result.errorMessage).contains("status").contains("message").contains("data");
    }

    @Test
    void strictMode_partialMissing_fails() {
        JsonNode decl = outputsDecl("status|number", "message|string", "data|object");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        parsed.put("message", "ok");
        // data 缺失

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.keyNameMismatched).containsExactly("data");
    }

    @Test
    void strictMode_nullValueTreatedAsMissing() {
        JsonNode decl = outputsDecl("status|number", "message|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", null);  // 键存在，但值为 null
        parsed.put("message", "ok");

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        // 键存在 → 不算 keyNameMismatched；值为 null → 进入 missingRequired
        assertThat(result.keyNameMismatched).isEmpty();
        assertThat(result.missingRequired).containsExactly("status");
    }

    @Test
    void strictMode_blankStringTreatedAsMissing() {
        JsonNode decl = outputsDecl("message|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("message", "   ");  // 空白字符串

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.missingRequired).containsExactly("message");
    }

    @Test
    void strictMode_typeMismatchStillConverts() {
        JsonNode decl = outputsDecl("age|number");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("age", "42");  // 字符串数字 → 自动转换

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
        assertThat(result.converted.get("age")).isInstanceOf(Number.class);
        assertThat(((Number) result.converted.get("age")).intValue()).isEqualTo(42);
    }

    @Test
    void strictMode_typeMismatchFailsWhenUnconvertible() {
        JsonNode decl = outputsDecl("age|number");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("age", List.of(1, 2, 3));  // 数组不能转 number

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.typeMismatched).anyMatch(s -> s.startsWith("age("));
    }

    @Test
    void strictMode_extraFieldsIgnored() {
        JsonNode decl = outputsDecl("status|number", "message|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        parsed.put("message", "ok");
        parsed.put("extra1", "ignored");
        parsed.put("extra2", Map.of("x", 1));

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
        // 额外字段保留在 converted 中（不裁剪）
        assertThat(result.converted).containsKey("extra1").containsKey("extra2");
    }

    @Test
    void strictMode_caseSensitive() {
        // 大小写敏感：Status ≠ status
        JsonNode decl = outputsDecl("status|number");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("Status", 200);

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.keyNameMismatched).containsExactly("status");
    }

    @Test
    void strictMode_separatorSensitive() {
        // 分隔符敏感：user-name ≠ userName
        JsonNode decl = outputsDecl("userName|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("user-name", "Alice");

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.keyNameMismatched).containsExactly("userName");
    }

    @Test
    void legacyMode_backwardCompatible() {
        // legacy 模式：未在 parsed 中的非必填字段不报错
        JsonNode decl = outputsDecl("name|string|required", "nickname|string|optional");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "Alice");
        // nickname 既不在 parsed 中，也未声明 required

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, false);

        assertThat(result.ok).isTrue();
        assertThat(result.keyNameMismatched).isEmpty();
        assertThat(result.missingRequired).isEmpty();
    }

    @Test
    void legacyMode_defaultOverloadEquivalentToFalse() {
        // 旧 2 参数重载等价于 strict=false
        JsonNode decl = outputsDecl("name|string|required");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "Alice");

        OutputValidator.ValidationResult r1 = OutputValidator.validate(parsed, decl);
        OutputValidator.ValidationResult r2 = OutputValidator.validate(parsed, decl, false);

        assertThat(r1.ok).isEqualTo(r2.ok);
        assertThat(r1.missingRequired).isEqualTo(r2.missingRequired);
        assertThat(r1.typeMismatched).isEqualTo(r2.typeMismatched);
        assertThat(r1.keyNameMismatched).isEqualTo(r2.keyNameMismatched);
    }

    @Test
    void strictMode_errorMessageListsKeyNameIssuesFirst() {
        // 错误信息优先级：键名缺失 > 值为空 > 类型不匹配
        JsonNode decl = outputsDecl("status|number", "message|string", "data|object");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("message", "   ");   // 值为空
        parsed.put("data", List.of(1)); // 类型不匹配（数组不能转 object）
        // status 完全缺失

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.errorMessage).contains("字段键名缺失")
                .contains("必填字段值为空")
                .contains("类型不匹配");
    }

    // ========== 新增：修订后语义覆盖 ==========

    @Test
    void strictMode_optionalFieldMissing_ok() {
        // strict 模式下，非 required 字段缺失不报错
        JsonNode decl = outputsDecl("status|number", "detail|string|optional");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        // detail 缺失

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
        assertThat(result.keyNameMismatched).isEmpty();
        assertThat(result.missingRequired).isEmpty();
    }

    @Test
    void strictMode_optionalFieldPresent_typeStillChecked() {
        // strict 模式下，非 required 字段若存在则做类型校验
        JsonNode decl = outputsDecl("status|number", "detail|object|optional");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        parsed.put("detail", List.of(1, 2));  // 数组不能转 object

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isFalse();
        assertThat(result.typeMismatched).anyMatch(s -> s.startsWith("detail("));
    }

    @Test
    void strictMode_llmSnakeCaseExtensions_ok() {
        // LLM 自由扩展的 snake_case 辅助字段不报错，与业务字段并存于 envelope.data
        JsonNode decl = outputsDecl("status|number", "message|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("status", 200);
        parsed.put("message", "ok");
        parsed.put("confidence", 0.95);
        parsed.put("run_error", "");
        parsed.put("user_notice", "请检查参数 X");

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
        // 扩展字段保留在 converted 中（不裁剪），供下游节点按需消费
        assertThat(result.converted).containsKey("confidence")
                .containsKey("run_error")
                .containsKey("user_notice");
    }

    @Test
    void strictMode_onlyRequiredChecked_whenAllOptional() {
        // 所有字段都是 optional 时，即使全部缺失也不报错
        JsonNode decl = outputsDecl("detail|string|optional", "extra|number|optional");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("anything", "whatever");  // 没有声明字段，但有扩展

        OutputValidator.ValidationResult result = OutputValidator.validate(parsed, decl, true);

        assertThat(result.ok).isTrue();
    }
}
