"""
测试双目录 skills 发现 —— 内置 skills + 用户自定义 skills
验证 discover_skills 同时扫描 skills_dir 和 user_skills_dir
"""
import sys
import tempfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

import pytest


def _create_skill_dir(parent: str, name: str, description: str = "Test skill") -> Path:
    """在 parent 下创建一个合法的 skill 目录（含 SKILL.md + main.py）"""
    parent = Path(parent)
    skill_dir = parent / name
    skill_dir.mkdir(parents=True, exist_ok=True)
    (skill_dir / "SKILL.md").write_text(f"## Description\n{description}\n\n## Example\nrun_skills('{name}', {{}})\n")
    (skill_dir / "main.py").write_text(f"async def run() -> dict:\n    return {{\"success\": True}}\n")
    return skill_dir


def _create_doc_skill_dir(parent: str, name: str, description: str = "Document skill") -> Path:
    parent = Path(parent)
    skill_dir = parent / name
    skill_dir.mkdir(parents=True, exist_ok=True)
    (skill_dir / "SKILL.md").write_text(f"## Description\n{description}\n\n## Workflow\n1. Step one\n2. Step two\n")
    return skill_dir


def _create_skill_dir_lowercase_md(parent: str, name: str, description: str = "Test skill") -> Path:
    parent = Path(parent)
    skill_dir = parent / name
    skill_dir.mkdir(parents=True, exist_ok=True)
    (skill_dir / "skill.md").write_text(f"## Description\n{description}\n\n## Example\nrun_skills('{name}', {{}})\n")
    (skill_dir / "main.py").write_text(f"async def run() -> dict:\n    return {{\"success\": True}}\n")
    return skill_dir


def _create_invalid_skill_dir(parent: str, name: str) -> Path:
    """在 parent 下创建一个没有 SKILL.md 的目录"""
    parent = Path(parent)
    skill_dir = parent / name
    skill_dir.mkdir(parents=True, exist_ok=True)
    (skill_dir / "main.py").write_text("async def run():\n    return {'success': True}\n")
    return skill_dir


def _make_manager(builtin_dir, user_dir):
    """创建一个 SkillsManager，使用临时目录作为 built-in skills 目录"""
    builtin_dir = Path(builtin_dir)
    user_dir = Path(user_dir)
    from aacode.tools.skills_tools import SkillsManager

    mgr = SkillsManager.__new__(SkillsManager)
    mgr.project_path = Path(".")
    mgr.skills_dir = builtin_dir
    mgr.user_skills_dir = user_dir
    mgr.loaded_skills = {}
    mgr.enabled_skills = []
    mgr.skills_metadata = {}
    return mgr


# ============================================================
# 1. SkillsManager 初始化
# ============================================================

class TestSkillsManagerInit:
    """验证 user_skills_dir 被正确设置"""

    def test_user_skills_dir_is_set(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("."))
        assert hasattr(mgr, "user_skills_dir")
        assert mgr.user_skills_dir.name == "skills"
        assert "com.aacode" in str(mgr.user_skills_dir)

    def test_skills_dir_is_set(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("."))
        assert hasattr(mgr, "skills_dir")
        assert mgr.skills_dir.name == "skills"

    def test_two_dirs_are_different(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("."))
        assert mgr.skills_dir != mgr.user_skills_dir


# ============================================================
# 2. _scan_skills_dir 扫描单个目录
# ============================================================

class TestScanSkillsDir:
    """验证 _scan_skills_dir 基本行为"""

    def test_scan_empty_dir_returns_zero(self):
        with tempfile.TemporaryDirectory() as td:
            mgr = _make_manager(Path(td), Path(td))
            count = mgr._scan_skills_dir(Path(td), load_full_instructions=False)
            assert count == 0
            assert len(mgr.loaded_skills) == 0

    def test_scan_nonexistent_dir_returns_zero(self):
        with tempfile.TemporaryDirectory() as td:
            mgr = _make_manager(Path(td), Path(td))
            nonexistent = Path(td) / "nonexistent"
            count = mgr._scan_skills_dir(nonexistent, load_full_instructions=False)
            assert count == 0
            assert len(mgr.loaded_skills) == 0

    def test_scan_dir_with_one_skill(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, "my_skill", "A custom skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "my_skill" in mgr.loaded_skills

    def test_scan_dir_with_multiple_skills(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, "skill_a", "Skill A")
            _create_skill_dir(builtin, "skill_b", "Skill B")
            _create_skill_dir(builtin, "skill_c", "Skill C")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 3
            assert "skill_a" in mgr.loaded_skills
            assert "skill_b" in mgr.loaded_skills
            assert "skill_c" in mgr.loaded_skills

    def test_scan_skips_dot_dirs(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, ".hidden_skill", "Hidden")
            _create_skill_dir(builtin, "normal_skill", "Normal")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "normal_skill" in mgr.loaded_skills
            assert ".hidden_skill" not in mgr.loaded_skills

    def test_scan_skips_underscore_dirs(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, "_private_skill", "Private")
            _create_skill_dir(builtin, "public_skill", "Public")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "public_skill" in mgr.loaded_skills
            assert "_private_skill" not in mgr.loaded_skills

    def test_scan_skips_dirs_without_skill_md(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_invalid_skill_dir(builtin, "no_md_skill")
            _create_skill_dir(builtin, "valid_skill", "Valid")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "valid_skill" in mgr.loaded_skills
            assert "no_md_skill" not in mgr.loaded_skills

    def test_scan_loads_doc_skill_as_expected(self):
        """SKILL.md 无 .py 的目录应作为文档型 skill 加载"""
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "md_only", "I am a doc skill")
            _create_skill_dir(builtin, "has_py", "Has py file")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 2
            assert "has_py" in mgr.loaded_skills
            assert "md_only" in mgr.loaded_skills
            assert mgr.loaded_skills["md_only"].is_document_skill is True
            assert mgr.loaded_skills["has_py"].is_document_skill is False

    def test_loaded_skill_has_correct_metadata(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, "my_skill", "This is my custom skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            info = mgr.loaded_skills["my_skill"]
            assert info.name == "my_skill"
            assert info.description == "This is my custom skill"
            assert info.metadata_loaded is True
            assert info.full_instruction_loaded is False


# ============================================================
# 3. discover_skills 双目录发现
# ============================================================

class TestDualDiscovery:
    """验证 discover_skills 同时扫描两个目录"""

    def test_builtin_only_when_user_dir_empty(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "builtin_a", "Builtin A")
            _create_skill_dir(builtin, "builtin_b", "Builtin B")
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert len(discovered) == 2
            assert "builtin_a" in discovered
            assert "builtin_b" in discovered

    def test_builtin_only_when_user_dir_nonexistent(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir(builtin, "builtin_a", "Builtin A")
            user_dir = Path(builtin) / "nonexistent_user"
            mgr = _make_manager(Path(builtin), user_dir)
            discovered = mgr.discover_skills()
            assert len(discovered) == 1
            assert "builtin_a" in discovered

    def test_user_skills_discovered_alongside_builtin(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "builtin_a", "Builtin A")
            _create_skill_dir(builtin, "builtin_b", "Builtin B")
            _create_skill_dir(user, "user_x", "User X")
            _create_skill_dir(user, "user_y", "User Y")
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert len(discovered) == 4
            assert "builtin_a" in discovered
            assert "builtin_b" in discovered
            assert "user_x" in discovered
            assert "user_y" in discovered

    def test_user_only_when_builtin_empty(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(user, "user_a", "User A")
            _create_skill_dir(user, "user_b", "User B")
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert len(discovered) == 2
            assert "user_a" in discovered
            assert "user_b" in discovered

    def test_user_skill_overrides_builtin_same_name(self):
        """用户 skill 与内置同名时应覆盖内置（后扫描的优先）"""
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "shared_name", "Builtin version")
            _create_skill_dir(user, "shared_name", "User version (override)")
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert "shared_name" in discovered
            # 用户版本覆盖内置版本（后扫描的 dict 赋值覆盖前一个）
            assert discovered["shared_name"].description == "User version (override)"

    def test_mixed_valid_and_invalid_user_skills(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "builtin_a", "Builtin A")
            _create_skill_dir(user, "user_valid", "Valid user skill")
            _create_invalid_skill_dir(user, "user_invalid")  # 无 SKILL.md
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert len(discovered) == 2
            assert "builtin_a" in discovered
            assert "user_valid" in discovered
            assert "user_invalid" not in discovered

    def test_discover_returns_dict_even_when_both_empty(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            mgr = _make_manager(Path(builtin), Path(user))
            discovered = mgr.discover_skills()
            assert isinstance(discovered, dict)
            assert len(discovered) == 0


# ============================================================
# 4. 真实系统集成测试 —— 确保现有 3 个内置 skills 仍在
# ============================================================

class TestRealBuiltinSkills:
    """验证真实的 3 个内置 skills 正常发现"""

    def test_three_builtin_skills_discovered(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("aacode"))
        discovered = mgr.discover_skills()
        assert "playwright" in discovered
        assert "pandas" in discovered
        assert "numpy" in discovered
        assert len(discovered) >= 3

    def test_builtin_skills_have_functions(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("aacode"))
        mgr.discover_skills()

        for name in ("pandas", "numpy"):
            mgr._load_full_instruction(name)
            info = mgr.loaded_skills[name]
            assert "run" in info.functions, f"{name}: missing 'run' function"

    def test_playwright_has_multiple_functions(self):
        from aacode.tools.skills_tools import SkillsManager
        mgr = SkillsManager(Path("aacode"))
        mgr.discover_skills()
        mgr._load_full_instruction("playwright")
        info = mgr.loaded_skills["playwright"]
        assert "run" in info.functions
        assert "browser_automation" in info.functions
        assert "scrape_dynamic_page" in info.functions
        assert "take_screenshot" in info.functions
        assert "test_element_exists" in info.functions


# ============================================================
# 5. enable_skills / list_enabled_skills 与双目录配合
# ============================================================

class TestEnableSkillsWithDualDiscovery:
    """验证 enable_skills 对双目录发现的 skills 生效"""

    def test_enable_all_discovered_skills(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "b1", "Builtin 1")
            _create_skill_dir(user, "u1", "User 1")
            mgr = _make_manager(Path(builtin), Path(user))
            mgr.discover_skills()
            mgr.enable_skills(["b1", "u1"])
            enabled = mgr.list_enabled_skills()
            assert "b1" in enabled
            assert "u1" in enabled

    def test_enable_only_user_skills(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "b1", "Builtin 1")
            _create_skill_dir(user, "u1", "User 1")
            mgr = _make_manager(Path(builtin), Path(user))
            mgr.discover_skills()
            mgr.enable_skills(["u1"])
            enabled = mgr.list_enabled_skills()
            assert "u1" in enabled
            assert "b1" not in enabled

    def test_discovered_skills_appear_in_prompt_list(self):
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "b1", "Builtin skill")
            _create_skill_dir(user, "u1", "User skill")
            mgr = _make_manager(Path(builtin), Path(user))
            mgr.discover_skills()
            mgr.enable_skills(["b1", "u1"])
            prompt_list = mgr.get_skills_list_for_prompt()
            assert "b1" in prompt_list
            assert "u1" in prompt_list


# ============================================================
# 6. 边界情况
# ============================================================

class TestEdgeCases:
    """边界情况测试"""

    def test_discover_twice_does_not_duplicate(self):
        """重复调用 discover_skills 不会产生重复条目"""
        with tempfile.TemporaryDirectory() as builtin, tempfile.TemporaryDirectory() as user:
            _create_skill_dir(builtin, "skill_a", "Skill A")
            _create_skill_dir(user, "skill_b", "Skill B")
            mgr = _make_manager(Path(builtin), Path(user))
            mgr.discover_skills()
            first_count = len(mgr.loaded_skills)
            mgr.discover_skills()
            assert len(mgr.loaded_skills) == first_count

    def test_empty_skill_dir_no_slash_issue(self):
        """空目录名不应引起路径问题（如 /）"""
        with tempfile.TemporaryDirectory() as builtin:
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            assert mgr.loaded_skills == {}


# ============================================================
# 7. Document skill (SKILL.md only, no .py) tests
# ============================================================

class TestDocumentSkills:
    """Verify document skill capabilities"""

    def test_doc_skill_discovered_and_enabled(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            assert "my_doc" in mgr.loaded_skills
            assert mgr.loaded_skills["my_doc"].is_document_skill is True
            assert mgr.loaded_skills["my_doc"].function_name == ""
            assert mgr.loaded_skills["my_doc"].functions == {}

    def test_doc_skill_full_instruction_loaded(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            assert mgr._load_full_instruction("my_doc") is True
            info = mgr.loaded_skills["my_doc"]
            assert "A document skill" in info.full_md_content
            assert "Step one" in info.full_md_content

    def test_doc_skill_prompt_list_includes_description(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            mgr.enable_skills(["my_doc"])
            prompt = mgr.get_skills_list_for_prompt()
            assert "my_doc" in prompt
            assert "A document skill" in prompt


# ============================================================
# 8. execute_skill document skill fallback tests
# ============================================================

class TestDocumentSkillExecute:
    """Verify executing a document skill returns SKILL.md content"""

    def test_execute_returns_md_content(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            mgr.enable_skills(["my_doc"])
            mgr._load_full_instruction("my_doc")
            import asyncio
            result = asyncio.run(mgr.execute_skill("my_doc"))
            assert result["success"] is True
            assert "A document skill" in result["result"]
            assert "Step one" in result["result"]

    def test_execute_with_params_shows_warning(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            mgr.enable_skills(["my_doc"])
            mgr._load_full_instruction("my_doc")
            import asyncio
            result = asyncio.run(mgr.execute_skill("my_doc", param1="value1"))
            assert result["success"] is True
            assert "Parameters were ignored" in result["result"]

    def test_execute_lists_resources_when_present(self):
        with tempfile.TemporaryDirectory() as builtin:
            skill_dir = Path(builtin) / "my_doc"
            skill_dir.mkdir(parents=True)
            (skill_dir / "SKILL.md").write_text("## Description\nA document skill\n\n## Workflow\n1. Step\n")
            (skill_dir / "reference.md").write_text("# Reference\nSome reference content\n")
            (skill_dir / "scripts").mkdir()
            (skill_dir / "scripts" / "helper.sh").write_text("#!/bin/bash\necho hello\n")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            mgr.enable_skills(["my_doc"])
            mgr._load_full_instruction("my_doc")
            import asyncio
            result = asyncio.run(mgr.execute_skill("my_doc"))
            assert result["success"] is True
            assert "Available Resources" in result["result"]
            assert "reference.md" in result["result"]
            assert "scripts/" in result["result"]

    def test_execute_no_resources_when_only_md(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_doc_skill_dir(builtin, "my_doc", "A document skill")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr.discover_skills()
            mgr.enable_skills(["my_doc"])
            mgr._load_full_instruction("my_doc")
            import asyncio
            result = asyncio.run(mgr.execute_skill("my_doc"))
            assert result["success"] is True
            assert "Available Resources" not in result["result"]


# ============================================================
# 9. Case-insensitive SKILL.md matching tests
# ============================================================

class TestCaseInsensitiveSkillMd:
    """Verify skill.md / Skill.md case variants are recognized"""

    def test_lowercase_skill_md_loaded(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir_lowercase_md(builtin, "lower_skill", "Lowercase md")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "lower_skill" in mgr.loaded_skills
            assert mgr.loaded_skills["lower_skill"].description == "Lowercase md"

    def test_lowercase_skill_md_full_instruction(self):
        with tempfile.TemporaryDirectory() as builtin:
            _create_skill_dir_lowercase_md(builtin, "lower_skill", "Lowercase md")
            mgr = _make_manager(Path(builtin), Path(builtin))
            mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert mgr._load_full_instruction("lower_skill") is True
            assert "Lowercase md" in mgr.loaded_skills["lower_skill"].full_md_content

    def test_mixed_case_skill_md_loaded(self):
        with tempfile.TemporaryDirectory() as builtin:
            skill_dir = Path(builtin) / "mixed_skill"
            skill_dir.mkdir(parents=True)
            (skill_dir / "Skill.md").write_text("## Description\nMixed case md\n\n## Example\nrun_skills('mixed_skill', {})\n")
            (skill_dir / "main.py").write_text("async def run() -> dict:\n    return {'success': True}\n")
            mgr = _make_manager(Path(builtin), Path(builtin))
            count = mgr._scan_skills_dir(Path(builtin), load_full_instructions=False)
            assert count == 1
            assert "mixed_skill" in mgr.loaded_skills
            assert mgr.loaded_skills["mixed_skill"].description == "Mixed case md"
