"""SKILL.md 解析器单元测试"""

import os
import tempfile

import pytest

from agentkit.skills.base import Skill, SkillConfig
from agentkit.skills.loader import SkillLoader
from agentkit.skills.registry import SkillRegistry
from agentkit.skills.skill_md import SkillMdParser


# ── 测试用 SKILL.md 内容 ──────────────────────────────────

FULL_SKILL_MD = '''\
---
name: content-generator
description: "Generate high-quality content based on requirements"
agent_type: content_generation
execution_mode: react
intent:
  keywords: ["generate", "write", "content"]
  description: "Content generation tasks"
  examples: ["Write a blog post", "Generate marketing copy"]
quality_gate:
  required_fields: ["content"]
  min_word_count: 100
  max_retries: 3
  custom_validator: "validators.check_quality"
---

# Trigger
- User asks to generate content
- Keywords: generate, write, create content

# Steps
1. Analyze the user's requirements and target audience
2. Research relevant topics and gather information
3. Draft the content following best practices
4. Review and refine the output

# Pitfalls
- Don't generate overly generic content
- Avoid plagiarism by always creating original content
- Don't ignore the target audience's preferences

# Verification
- Content meets minimum word count
- Content is relevant to the user's request
- Output format matches expectations
'''

MINIMAL_SKILL_MD = '''\
---
name: minimal-skill
description: "A minimal skill"
agent_type: minimal
---

# Steps
1. Do something
'''

NO_FRONTMATTER_MD = '''\
# Steps
1. Step one
2. Step two
'''

EMPTY_FRONTMATTER_MD = '''\
---
---

# Steps
1. Step one
'''


def _write_skill_md(directory: str, filename: str, content: str) -> str:
    path = os.path.join(directory, filename)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)
    return path


# ── SkillMdParser.parse 测试 ──────────────────────────────


class TestSkillMdParserParse:
    """SkillMdParser.parse() 解析测试"""

    def test_parse_full_skill_md(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "content.md", FULL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)

            assert frontmatter["name"] == "content-generator"
            assert frontmatter["description"] == "Generate high-quality content based on requirements"
            assert frontmatter["agent_type"] == "content_generation"
            assert frontmatter["execution_mode"] == "react"
            assert frontmatter["intent"]["keywords"] == ["generate", "write", "content"]
            assert frontmatter["quality_gate"]["required_fields"] == ["content"]
            assert frontmatter["quality_gate"]["min_word_count"] == 100

            assert "trigger" in sections
            assert "steps" in sections
            assert "pitfalls" in sections
            assert "verification" in sections
            assert "Analyze the user's requirements" in sections["steps"]
            assert "Don't generate overly generic content" in sections["pitfalls"]

            assert "Trigger" not in body or "# Trigger" in body

    def test_parse_minimal_skill_md(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "minimal.md", MINIMAL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)

            assert frontmatter["name"] == "minimal-skill"
            assert frontmatter["description"] == "A minimal skill"
            assert "steps" in sections
            assert "Do something" in sections["steps"]

    def test_parse_no_frontmatter(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "no_fm.md", NO_FRONTMATTER_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)

            assert frontmatter == {}
            assert "steps" in sections

    def test_parse_empty_frontmatter(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "empty_fm.md", EMPTY_FRONTMATTER_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)

            assert frontmatter == {}
            assert "steps" in sections

    def test_parse_missing_sections_graceful(self):
        content = """\
---
name: no-sections
description: "No body sections"
agent_type: test
---
"""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "nosec.md", content)
            frontmatter, sections, body = SkillMdParser.parse(path)

            assert frontmatter["name"] == "no-sections"
            assert sections == {}


# ── SkillMdParser.to_skill_config 测试 ────────────────────


class TestSkillMdToSkillConfig:
    """SkillMdParser.to_skill_config() 转换测试"""

    def test_to_skill_config_full(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "full.md", FULL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            config = SkillMdParser.to_skill_config(frontmatter, sections, path)

            assert config.name == "content-generator"
            assert config.agent_type == "content_generation"
            assert config.description == "Generate high-quality content based on requirements"
            assert config.execution_mode == "react"
            assert config.intent.keywords == ["generate", "write", "content"]
            assert config.intent.description == "Content generation tasks"
            assert config.intent.examples == ["Write a blog post", "Generate marketing copy"]
            assert config.quality_gate.required_fields == ["content"]
            assert config.quality_gate.min_word_count == 100
            assert config.quality_gate.max_retries == 3
            assert config.quality_gate.custom_validator == "validators.check_quality"
            assert config.prompt is not None
            assert "instructions" in config.prompt
            assert "constraints" in config.prompt
            assert "output_format" in config.prompt
            assert "context" in config.prompt

    def test_to_skill_config_level_0_summary_only(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "level0.md", FULL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            config = SkillMdParser.to_skill_config(
                frontmatter, sections, path, disclosure_level=0,
            )

            assert config.name == "content-generator"
            assert config.description != ""
            assert config.disclosure_level == 0
            # Level 0: prompt 仅含 identity（概要信息）
            assert config.prompt is not None
            assert "identity" in config.prompt
            assert "instructions" not in config.prompt

    def test_to_skill_config_level_1_full(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "level1.md", FULL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            config = SkillMdParser.to_skill_config(
                frontmatter, sections, path, disclosure_level=1,
            )

            assert config.name == "content-generator"
            assert config.disclosure_level == 1
            assert config.prompt is not None
            assert "instructions" in config.prompt

    def test_to_skill_config_minimal(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "minimal.md", MINIMAL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            config = SkillMdParser.to_skill_config(frontmatter, sections, path)

            assert config.name == "minimal-skill"
            assert config.agent_type == "minimal"
            assert config.execution_mode == "react"  # 默认值
            assert config.intent.keywords == []
            assert config.quality_gate.required_fields == []

    def test_to_skill_config_no_frontmatter(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "no_fm.md", NO_FRONTMATTER_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            # 无 frontmatter 时 name 为空，无法创建有效的 SkillConfig
            # 验证解析结果正确即可
            assert frontmatter == {}
            assert "steps" in sections

    def test_skill_md_path_stored(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "path_test.md", FULL_SKILL_MD)
            frontmatter, sections, body = SkillMdParser.parse(path)
            config = SkillMdParser.to_skill_config(frontmatter, sections, path)

            assert config.skill_md_path == path


# ── SkillConfig 新字段测试 ─────────────────────────────────


class TestSkillConfigNewFields:
    """SkillConfig 新增 skill_md_path 和 disclosure_level 字段测试"""

    def test_default_skill_md_path_is_none(self):
        config = SkillConfig(
            name="test",
            agent_type="test",
            task_mode="llm_generate",
            prompt={"identity": "test"},
        )
        assert config.skill_md_path is None

    def test_default_disclosure_level_is_zero(self):
        config = SkillConfig(
            name="test",
            agent_type="test",
            task_mode="llm_generate",
            prompt={"identity": "test"},
        )
        assert config.disclosure_level == 0

    def test_skill_md_path_set(self):
        config = SkillConfig(
            name="test",
            agent_type="test",
            task_mode="llm_generate",
            prompt={"identity": "test"},
            skill_md_path="/path/to/skill.md",
        )
        assert config.skill_md_path == "/path/to/skill.md"

    def test_disclosure_level_set(self):
        config = SkillConfig(
            name="test",
            agent_type="test",
            task_mode="llm_generate",
            prompt={"identity": "test"},
            disclosure_level=2,
        )
        assert config.disclosure_level == 2

    def test_to_dict_includes_new_fields(self):
        config = SkillConfig(
            name="test",
            agent_type="test",
            task_mode="llm_generate",
            prompt={"identity": "test"},
            skill_md_path="/path/to/skill.md",
            disclosure_level=1,
        )
        d = config.to_dict()
        assert d["skill_md_path"] == "/path/to/skill.md"
        assert d["disclosure_level"] == 1

    def test_from_dict_includes_new_fields(self):
        data = {
            "name": "test",
            "agent_type": "test",
            "task_mode": "llm_generate",
            "prompt": {"identity": "test"},
            "skill_md_path": "/path/to/skill.md",
            "disclosure_level": 2,
        }
        config = SkillConfig.from_dict(data)
        assert config.skill_md_path == "/path/to/skill.md"
        assert config.disclosure_level == 2

    def test_from_dict_defaults_new_fields(self):
        data = {
            "name": "test",
            "agent_type": "test",
            "task_mode": "llm_generate",
            "prompt": {"identity": "test"},
        }
        config = SkillConfig.from_dict(data)
        assert config.skill_md_path is None
        assert config.disclosure_level == 0


# ── SkillLoader.load_from_skill_md 测试 ───────────────────


class TestSkillLoaderFromSkillMd:
    """SkillLoader.load_from_skill_md() 加载测试"""

    def test_load_from_skill_md_creates_skill(self):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "content.md", FULL_SKILL_MD)
            skill = loader.load_from_skill_md(path)

            assert isinstance(skill, Skill)
            assert skill.name == "content-generator"
            assert skill.config.agent_type == "content_generation"
            assert skill.config.skill_md_path == path
            assert skill.config.disclosure_level == 1  # 默认 level=1

    def test_load_from_skill_md_registers_in_registry(self):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "content.md", FULL_SKILL_MD)
            loader.load_from_skill_md(path)

            assert registry.has_skill("content-generator")

    def test_load_from_skill_md_level_0(self):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "content.md", FULL_SKILL_MD)
            skill = loader.load_from_skill_md(path, disclosure_level=0)

            assert skill.config.disclosure_level == 0
            # Level 0: prompt 仅含 identity，不含 instructions
            assert skill.config.prompt is not None
            assert "identity" in skill.config.prompt
            assert "instructions" not in skill.config.prompt

    def test_load_from_skill_md_level_1(self):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            path = _write_skill_md(tmpdir, "content.md", FULL_SKILL_MD)
            skill = loader.load_from_skill_md(path, disclosure_level=1)

            assert skill.config.disclosure_level == 1
            assert skill.config.prompt is not None
            assert "instructions" in skill.config.prompt

    def test_load_from_directory_includes_md_files(self):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            _write_skill_md(tmpdir, "skill.md", FULL_SKILL_MD)
            skills = loader.load_from_directory(tmpdir)

            assert len(skills) == 1
            assert skills[0].name == "content-generator"

    def test_load_from_directory_mixed_yaml_and_md(self):
        import yaml

        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            # YAML 文件
            yaml_path = os.path.join(tmpdir, "yaml_skill.yaml")
            with open(yaml_path, "w", encoding="utf-8") as f:
                yaml.dump({
                    "name": "yaml_skill",
                    "agent_type": "yaml",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "YAML 技能"},
                }, f)

            # SKILL.md 文件
            _write_skill_md(tmpdir, "md_skill.md", FULL_SKILL_MD)

            skills = loader.load_from_directory(tmpdir)
            assert len(skills) == 2
            names = [s.name for s in skills]
            assert "yaml_skill" in names
            assert "content-generator" in names

    def test_load_from_directory_skips_invalid_md(self, caplog):
        registry = SkillRegistry()
        loader = SkillLoader(skill_registry=registry)

        with tempfile.TemporaryDirectory() as tmpdir:
            # 无效的 MD（不是合法的 SKILL.md 格式，YAML 解析后缺少必要字段）
            invalid_md = "This is just plain text, not a valid SKILL.md at all."
            _write_skill_md(tmpdir, "invalid.md", invalid_md)

            with caplog.at_level("WARNING"):
                skills = loader.load_from_directory(tmpdir)

            # 无效文件应被跳过（纯文本无 frontmatter，name 为空）
            assert len(skills) == 0


# ── CLI skill create 测试 ─────────────────────────────────


class TestCliSkillCreate:
    """CLI skill create 命令测试"""

    def test_create_generates_valid_skill_md(self):
        from typer.testing import CliRunner
        from agentkit.cli.skill import skill_app

        runner = CliRunner()
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(skill_app, ["create", "my-skill", "--output-dir", tmpdir])
            assert result.exit_code == 0

            output_path = os.path.join(tmpdir, "my-skill.md")
            assert os.path.exists(output_path)

            # 验证生成的文件可以被解析
            frontmatter, sections, body = SkillMdParser.parse(output_path)
            assert frontmatter["name"] == "my-skill"
            assert "steps" in sections
            assert "pitfalls" in sections
            assert "verification" in sections

    def test_create_template_is_loadable(self):
        from typer.testing import CliRunner
        from agentkit.cli.skill import skill_app

        runner = CliRunner()
        with tempfile.TemporaryDirectory() as tmpdir:
            runner.invoke(skill_app, ["create", "loadable-skill", "--output-dir", tmpdir])

            output_path = os.path.join(tmpdir, "loadable-skill.md")
            registry = SkillRegistry()
            loader = SkillLoader(skill_registry=registry)
            skill = loader.load_from_skill_md(output_path)

            assert skill.name == "loadable-skill"
