package com.easyagents.skill.util;

import com.easyagents.skill.exception.SkillValidationException;
import com.easyagents.skill.model.SkillDocument;
import com.easyagents.skill.model.SkillPackageLimits;
import org.junit.Assert;
import org.junit.Test;

import java.util.List;
import java.util.Map;

/**
 * SkillFrontmatter 安全解析与 round-trip 测试。
 */
public class SkillFrontmatterTest {

    /**
     * 嵌套结构、未知字段和插入顺序在序列化后保持语义。
     */
    @Test
    public void nestedMetadataRoundTrips() {
        String content = "---\nname: nested-skill\ndescription: Handles nested values\n"
                + "enabled: true\ncount: 3\nnullable: null\n"
                + "metadata:\n  owner: team-a\n  tags:\n    - one\n    - two\n"
                + "---\n# Nested\n";

        SkillDocument first = SkillFrontmatter.parseDocument(content);
        String serialized = SkillFrontmatter.serialize(first.getFrontmatter().getValues(), first.getMarkdownBody());
        SkillDocument second = SkillFrontmatter.parseDocument(serialized);

        Assert.assertEquals(first.getFrontmatter().getValues(), second.getFrontmatter().getValues());
        Assert.assertEquals(first.getMarkdownBody(), second.getMarkdownBody());
        Assert.assertEquals(List.of("name", "description", "enabled", "count", "nullable", "metadata"),
                second.getFrontmatter().getValues().keySet().stream().toList());
    }

    /**
     * 未编辑文档原样返回，结构化编辑后重新生成合法文档。
     */
    @Test
    public void preserveRawUntilEdited() {
        String content = "---\r\nname: raw-skill\r\ndescription: Keep source\r\n---\r\n# Raw\r\n";
        SkillDocument document = SkillFrontmatter.parseDocument(content);

        Assert.assertEquals(content, document.render());

        document.putFrontmatter("metadata", Map.of("level", 2));
        Assert.assertNotEquals(content, document.render());
        SkillDocument parsed = SkillFrontmatter.parseDocument(document.render());
        Assert.assertEquals("# Raw\r\n", parsed.getMarkdownBody());
        Assert.assertTrue(parsed.getFrontmatter().get("metadata") instanceof Map);
    }

    /**
     * 顶层字段保留完整 SKILL.md 中的一基源码位置，结构化修改后移除失效位置。
     */
    @Test
    public void retainTopLevelSourceLocationsUntilFieldEdited() {
        String content = "---\nname: located-skill\ndescription: Keep positions\n"
                + "metadata:\n  owner: team-a\n---\n# Located\n";

        SkillDocument document = SkillFrontmatter.parseDocument(content);

        Assert.assertEquals(2, document.getFrontmatterLocation("name").getLine());
        Assert.assertEquals(1, document.getFrontmatterLocation("name").getColumn());
        Assert.assertEquals(3, document.getFrontmatterLocation("description").getLine());
        Assert.assertEquals(1, document.getFrontmatterLocation("description").getColumn());
        Assert.assertEquals(4, document.getFrontmatterLocation("metadata").getLine());
        Assert.assertEquals(1, document.getFrontmatterLocation("metadata").getColumn());
        Assert.assertTrue(document.getDiagnostics().isEmpty());

        document.putFrontmatter("description", "Updated description");

        Assert.assertNull(document.getFrontmatterLocation("description"));
        Assert.assertNotNull(document.getFrontmatterLocation("name"));
    }

    /**
     * 重复 key 被安全构造器拒绝。
     */
    @Test
    public void rejectDuplicateKeys() {
        assertCode("INVALID_FRONTMATTER_YAML",
                "---\nname: duplicate-skill\nname: other\ndescription: Duplicate\n---\n");
    }

    /**
     * 自定义危险 tag 被拒绝。
     */
    @Test
    public void rejectCustomTag() {
        assertCode("INVALID_FRONTMATTER_YAML",
                "---\nname: tagged-skill\ndescription: Tagged\nvalue: !java/object test\n---\n");
    }

    /**
     * yaml.org 前缀下不在白名单中的 core tag 也被拒绝。
     */
    @Test
    public void rejectDisallowedYamlOrgTag() {
        assertCode("UNSUPPORTED_FRONTMATTER_TYPE",
                "---\nname: binary-skill\ndescription: Binary tag\nvalue: !!binary YWJj\n---\n");
    }

    /**
     * 未知 yaml.org 全局 tag 被 TagInspector 拒绝。
     */
    @Test
    public void rejectUnknownYamlOrgTag() {
        assertCode("INVALID_FRONTMATTER_YAML",
                "---\nname: unknown-skill\ndescription: Unknown tag\nvalue: !!unknown test\n---\n");
    }

    /**
     * 非 Map 顶层 YAML 被拒绝。
     */
    @Test
    public void rejectNonMapRoot() {
        assertCode("FRONTMATTER_ROOT_NOT_MAP", "---\n- one\n- two\n---\n");
    }

    /**
     * 嵌套 Map 的非字符串 key 被拒绝且不做静默字符串化。
     */
    @Test
    public void rejectNestedNonStringKey() {
        assertCode("INVALID_FRONTMATTER_KEY",
                "---\nname: key-skill\ndescription: Invalid key\nmetadata:\n  1: value\n---\n");
    }

    /**
     * 可配置 frontmatter 字节上限生效。
     */
    @Test
    public void rejectOversizedFrontmatter() {
        SkillPackageLimits limits = SkillPackageLimits.builder().maxFrontmatterBytes(64).build();
        try {
            SkillFrontmatter.parseDocument("---\nname: large-skill\ndescription: " + "x".repeat(80)
                    + "\n---\n", limits);
            Assert.fail("Oversized frontmatter should fail.");
        } catch (SkillValidationException e) {
            Assert.assertEquals("FRONTMATTER_TOO_LARGE", e.getCode());
        }
    }

    /**
     * YAML collection alias 上限生效。
     */
    @Test
    public void rejectAliasLimit() {
        String content = "---\nname: alias-skill\ndescription: Alias limit\n"
                + "base: &base\n  - one\naliases:\n  - *base\n  - *base\n---\n";
        assertCode("INVALID_FRONTMATTER_YAML", content,
                SkillPackageLimits.builder().maxYamlAliases(1).build());
    }

    /**
     * YAML 嵌套深度上限生效。
     */
    @Test
    public void rejectDepthLimit() {
        String content = "---\nname: depth-skill\ndescription: Depth limit\n"
                + "metadata:\n  one:\n    two:\n      three: value\n---\n";
        assertCode("INVALID_FRONTMATTER_YAML", content,
                SkillPackageLimits.builder().maxYamlDepth(3).build());
    }

    /**
     * YAML code point 上限生效。
     */
    @Test
    public void rejectCodePointLimit() {
        String content = "---\nname: codepoint-skill\ndescription: " + "x".repeat(100) + "\n---\n";
        assertCode("INVALID_FRONTMATTER_YAML", content,
                SkillPackageLimits.builder().maxYamlCodePoints(64).build());
    }

    /**
     * 未闭合 frontmatter 返回稳定错误码。
     */
    @Test
    public void rejectUnclosedFrontmatter() {
        assertCode("FRONTMATTER_NOT_CLOSED",
                "---\nname: unclosed-skill\ndescription: Unclosed\n");
    }

    private static void assertCode(String code, String content) {
        assertCode(code, content, SkillPackageLimits.defaults());
    }

    private static void assertCode(String code, String content, SkillPackageLimits limits) {
        try {
            SkillFrontmatter.parseDocument(content, limits);
            Assert.fail("Invalid frontmatter should fail.");
        } catch (SkillValidationException e) {
            Assert.assertEquals(code, e.getCode());
        }
    }
}
