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} 单元测试。
 *
 * <p>覆盖：必填校验、类型校验、类型自动转换、综合场景。</p>
 */
class OutputValidatorTest {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static JsonNode outputsDecl(String... fields) {
        // fields 形如 "name|type|required" 或 "name|type"
        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("\"");
            if (parts.length > 2 && "required".equalsIgnoreCase(parts[2])) {
                sb.append(",\"required\":true");
            }
            sb.append("}");
        }
        sb.append("]");
        try {
            return MAPPER.readTree(sb.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    void allRequiredPresentAndTypesMatchPasses() {
        JsonNode decl = outputsDecl("name|string|required", "age|number");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "Alice");
        parsed.put("age", 18);

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

        assertThat(result.ok).isTrue();
        assertThat(result.missingRequired).isEmpty();
        assertThat(result.typeMismatched).isEmpty();
        assertThat(result.converted).containsEntry("name", "Alice").containsEntry("age", 18);
    }

    @Test
    void missingRequiredFieldAddedToMissingList() {
        JsonNode decl = outputsDecl("name|string|required", "age|number|required");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "Alice");
        // age 缺失

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

        assertThat(result.ok).isFalse();
        assertThat(result.missingRequired).containsExactly("age");
        assertThat(result.typeMismatched).isEmpty();
    }

    @Test
    void nullRequiredFieldTreatedAsMissing() {
        JsonNode decl = outputsDecl("name|string|required");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", null);

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

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

    @Test
    void blankStringRequiredFieldTreatedAsMissing() {
        JsonNode decl = outputsDecl("name|string|required");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "   ");

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

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

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

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

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

    @Test
    void typeMismatchBooleanWithObjectAddedToMismatched() {
        JsonNode decl = outputsDecl("active|boolean");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("active", Map.of("x", 1));  // object 不能转 boolean

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

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

    @Test
    void autoConvertStringNumberToNumber() {
        JsonNode decl = outputsDecl("age|number");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("age", "42");

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

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

    @Test
    void autoConvertStringTrueToBoolean() {
        JsonNode decl = outputsDecl("active|boolean");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("active", "true");

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

        assertThat(result.ok).isTrue();
        assertThat(result.converted.get("active")).isEqualTo(Boolean.TRUE);
    }

    @Test
    void autoConvertSingleValueToArray() {
        JsonNode decl = outputsDecl("tags|array");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("tags", "hello");

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

        assertThat(result.ok).isTrue();
        assertThat(result.converted.get("tags")).isInstanceOf(List.class);
        @SuppressWarnings("unchecked")
        List<Object> tagsList = (List<Object>) result.converted.get("tags");
        assertThat(tagsList).containsExactly("hello");
    }

    @Test
    void anyValueConvertsToString() {
        JsonNode decl = outputsDecl("label|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("label", 42);

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

        assertThat(result.ok).isTrue();
        assertThat(result.converted.get("label")).isEqualTo("42");
    }

    @Test
    void nonRequiredNullSkipped() {
        JsonNode decl = outputsDecl("name|string|required", "nickname|string");
        Map<String, Object> parsed = new LinkedHashMap<>();
        parsed.put("name", "Alice");
        // nickname 为 null 但非必填 → 跳过

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

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

    @Test
    void emptyOutputsDeclAlwaysOk() {
        JsonNode decl = outputsDecl();
        Map<String, Object> parsed = Map.of("anything", "value");

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

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

    @Test
    void nullParsedReturnsFail() {
        JsonNode decl = outputsDecl("name|string|required");

        OutputValidator.ValidationResult result = OutputValidator.validate(null, decl);

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

    @Test
    void combinedMissingAndMismatchReported() {
        JsonNode decl = outputsDecl(
                "name|string|required",
                "age|number|required",
                "tags|array",
                "active|boolean"
        );
        Map<String, Object> parsed = new LinkedHashMap<>();
        // name 缺失 → missingRequired
        parsed.put("age", List.of(1, 2));  // 类型不匹配 → typeMismatched
        parsed.put("tags", "single");      // 自动转换 OK
        parsed.put("active", Map.of("x", 1)); // 类型不匹配 → typeMismatched

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

        assertThat(result.ok).isFalse();
        assertThat(result.missingRequired).containsExactly("name");
        assertThat(result.typeMismatched).hasSize(2);
    }
}
