package com.agent.management.engine;

import com.fasterxml.jackson.databind.JsonNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 节点输出校验器。
 *
 * <p>对 LLM 解析后的 {@code Map<varName, value>} 与节点 outputs 声明进行结构校验：</p>
 * <ul>
 *   <li><b>键名缺失</b>（仅 strict 模式）：声明字段在 parsed 中不存在（精确键匹配）</li>
 *   <li><b>必填字段缺失</b>：{@code outputs[i].required == true} 但值为 null 或空字符串</li>
 *   <li><b>类型不匹配</b>：声明为 {@code number/array/object/boolean} 但值类型不符且无法转换</li>
 * </ul>
 *
 * <p>类型自动转换（不算 mismatch）：</p>
 * <ul>
 *   <li>{@code number} + 字符串数字 → 静默转 Number</li>
 *   <li>{@code boolean} + "true"/"false" 字符串 → 静默转 Boolean</li>
 *   <li>{@code string} + 任意 → 静默转 String（由 VariableConverter 处理）</li>
 * </ul>
 *
 * <p>本类返回 {@link ValidationResult}，由调用方（StructuredOutputHelper）决定是否触发重试。</p>
 *
 * <p><b>strict 模式</b>（{@link #validate(Map, JsonNode, boolean)}）：</p>
 * <ul>
 *   <li>只校验 {@code required=true} 字段：必须精确存在（区分大小写、区分分隔符）且值非空</li>
 *   <li>非 required 字段：缺失不报错；存在则做类型校验</li>
 *   <li>多余字段（含 LLM 自由扩展的 snake_case 辅助字段）完全不报错</li>
 *   <li>{@code strict} 参数仅用于语义标记，不再把所有声明字段视为必填</li>
 * </ul>
 */
public final class OutputValidator {

    private OutputValidator() {}

    public static final class ValidationResult {
        public final boolean ok;
        public final String errorMessage;
        public final List<String> missingRequired;
        public final List<String> typeMismatched;
        /** strict 模式下，声明字段在 parsed 中不存在（精确键匹配失败）的字段名列表。 */
        public final List<String> keyNameMismatched;
        /** 类型自动转换后的新 Map（即使 ok 也可能含转换后的值）。 */
        public final Map<String, Object> converted;

        private ValidationResult(boolean ok, String errorMessage,
                                  List<String> missingRequired, List<String> typeMismatched,
                                  List<String> keyNameMismatched,
                                  Map<String, Object> converted) {
            this.ok = ok;
            this.errorMessage = errorMessage;
            this.missingRequired = missingRequired == null ? Collections.emptyList() : missingRequired;
            this.typeMismatched = typeMismatched == null ? Collections.emptyList() : typeMismatched;
            this.keyNameMismatched = keyNameMismatched == null ? Collections.emptyList() : keyNameMismatched;
            this.converted = converted == null ? Collections.emptyMap() : converted;
        }

        public static ValidationResult ok(Map<String, Object> converted) {
            return new ValidationResult(true, null,
                    Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), converted);
        }

        public static ValidationResult fail(List<String> missing, List<String> mismatched,
                                             List<String> keyNameMismatched, Map<String, Object> converted) {
            List<String> parts = new ArrayList<>();
            if (!keyNameMismatched.isEmpty()) {
                parts.add("字段键名缺失（必须精确匹配）: " + String.join(", ", keyNameMismatched));
            }
            if (!missing.isEmpty()) {
                parts.add("必填字段值为空: " + String.join(", ", missing));
            }
            if (!mismatched.isEmpty()) {
                parts.add("类型不匹配: " + String.join(", ", mismatched));
            }
            return new ValidationResult(false, String.join("; ", parts),
                    missing, mismatched, keyNameMismatched, converted);
        }
    }

    /**
     * 校验解析后的 Map（legacy 模式，与 strict=false 等价）。
     */
    public static ValidationResult validate(Map<String, Object> parsed, JsonNode outputsDecl) {
        return validate(parsed, outputsDecl, false);
    }

    /**
     * 校验解析后的 Map。null 视为完全解析失败（不进入本方法）。
     *
     * @param parsed      LLM 响应解析后的 Map
     * @param outputsDecl 节点 outputs 声明数组
     * @param strict      true 时 required 字段必须精确存在（区分大小写、区分分隔符）；
     *                    false 时仅按 parsed 自身内容判定；
     *                    两种模式都不报多余字段、都不把非 required 字段视为必填
     */
    public static ValidationResult validate(Map<String, Object> parsed, JsonNode outputsDecl,
                                            boolean strict) {
        if (parsed == null) {
            return ValidationResult.fail(Collections.emptyList(), Collections.emptyList(),
                    Collections.emptyList(), Collections.emptyMap());
        }
        if (outputsDecl == null || !outputsDecl.isArray() || outputsDecl.isEmpty()) {
            // 无声明：不校验
            return ValidationResult.ok(parsed);
        }

        List<String> missing = new ArrayList<>();
        List<String> mismatched = new ArrayList<>();
        List<String> keyNameMissing = new ArrayList<>();
        Map<String, Object> converted = new LinkedHashMap<>(parsed);

        for (JsonNode decl : outputsDecl) {
            String name = decl.path("name").asText("");
            if (name.isEmpty()) continue;
            String type = decl.path("type").asText("string");
            boolean required = decl.path("required").asBoolean(false);

            // strict 模式：required 字段必须精确存在（区分大小写、区分分隔符）
            if (strict && required && !converted.containsKey(name)) {
                keyNameMissing.add(name);
                continue;
            }

            Object value = converted.get(name);

            // 必填校验（值为 null 或空字符串）
            if (required && (value == null || isBlankString(value))) {
                missing.add(name);
                continue;
            }
            if (value == null) {
                continue; // 非必填且无值，跳过类型校验
            }

            // 类型校验（含自动转换）
            Object convertedValue = checkAndConvertType(value, type);
            if (convertedValue == TYPE_MISMATCH_SENTINEL) {
                mismatched.add(name + "(" + type + ")");
            } else if (convertedValue != value) {
                converted.put(name, convertedValue);
            }
        }

        if (missing.isEmpty() && mismatched.isEmpty() && keyNameMissing.isEmpty()) {
            return ValidationResult.ok(converted);
        }
        return ValidationResult.fail(missing, mismatched, keyNameMissing, converted);
    }

    private static final Object TYPE_MISMATCH_SENTINEL = new Object();

    private static boolean isBlankString(Object value) {
        return value instanceof String && ((String) value).isBlank();
    }

    /**
     * 检查并尝试类型转换。返回：
     * <ul>
     *   <li>原值（无需转换）</li>
     *   <li>转换后的值（成功转换）</li>
     *   <li>{@link #TYPE_MISMATCH_SENTINEL}（无法转换）</li>
     * </ul>
     */
    private static Object checkAndConvertType(Object value, String targetType) {
        switch (targetType) {
            case "string":
                // 任意都可转 string，但只有非 String 才需要转
                return value instanceof String ? value : value.toString();
            case "number":
                if (value instanceof Number) return value;
                if (value instanceof String) {
                    String s = (String) value;
                    try { return s.contains(".") ? Double.parseDouble(s) : Long.parseLong(s); }
                    catch (NumberFormatException e) { return TYPE_MISMATCH_SENTINEL; }
                }
                return TYPE_MISMATCH_SENTINEL;
            case "boolean":
                if (value instanceof Boolean) return value;
                if (value instanceof String) {
                    String s = ((String) value).toLowerCase();
                    if ("true".equals(s)) return Boolean.TRUE;
                    if ("false".equals(s)) return Boolean.FALSE;
                }
                return TYPE_MISMATCH_SENTINEL;
            case "array":
                if (value instanceof List<?>) return value;
                if (value != null) {
                    // 兼容：单值包装为单元素 List
                    List<Object> wrapped = new ArrayList<>();
                    wrapped.add(value);
                    return wrapped;
                }
                return TYPE_MISMATCH_SENTINEL;
            case "object":
                if (value instanceof Map<?, ?>) return value;
                return TYPE_MISMATCH_SENTINEL;
            default:
                // 其他类型（filePath / directoryPath 等）视为 string 子类
                return value;
        }
    }
}
