"""
综合测试：Skill 系统 —— 发现、命名、执行、Schema暴露、prompt替换
"""
import sys
import os
import asyncio
import tempfile
from pathlib import Path

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

import pytest


# ============================================================
# 1. Skill 文件发现 & SKILL.md 解析
# ============================================================

class TestSkillDiscovery:
    """技能自动发现和 MD 解析"""

    def test_discover_all_skills(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        discovered = sm.discover_skills()

        assert "pandas" in discovered
        assert "numpy" in discovered
        assert "playwright" in discovered
        assert len(discovered) >= 3

    def test_discover_pandas_has_correct_metadata(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        discovered = sm.discover_skills()

        pandas = discovered["pandas"]
        assert pandas.display_name == "Pandas"
        assert "pandas" in pandas.description.lower()
        assert pandas.metadata_loaded is True

    def test_discover_numpy_has_correct_metadata(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        discovered = sm.discover_skills()

        numpy = discovered["numpy"]
        assert numpy.display_name == "Numpy"
        assert "numpy" in numpy.description.lower()

    # ---- 函数名提取 (验证 rename 生效) ----

    def test_pandas_function_name_is_run(self):
        """pandas skill 的函数名应为 run（而非 run_pandas）"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()

        sm._load_full_instruction("pandas")
        info = sm.loaded_skills["pandas"]

        assert "run" in info.functions, f"期望 functions 中有 'run', 实际: {list(info.functions.keys())}"
        assert "run_pandas" not in info.functions

    def test_numpy_function_name_is_run(self):
        """numpy skill 的函数名应为 run（而非 run_numpy）"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()

        sm._load_full_instruction("numpy")
        info = sm.loaded_skills["numpy"]

        assert "run" in info.functions, f"期望 functions 中有 'run', 实际: {list(info.functions.keys())}"
        assert "run_numpy" not in info.functions

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

        sm._load_full_instruction("playwright")
        info = sm.loaded_skills["playwright"]

        assert len(info.functions) >= 3
        for func_name in ("browser_automation", "scrape_dynamic_page", "take_screenshot"):
            assert func_name in info.functions


# ============================================================
# 2. run_skills 工具统一入口验证
# ============================================================

class TestRunSkillsUnifiedEntry:
    """验证 run_skills 作为唯一的技能入口"""

    def test_run_skills_in_agent_tools(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard
        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)
        model_config = {"name": "deepseek-chat", "api_key": "test_key", "base_url": "https://api.deepseek.com/v1", "gateway": "openai", "temperature": 0.7}
        agent = MainAgent(project_path, cm, sg, model_config)

        assert "run_skills" in agent.tools
        assert callable(agent.tools["run_skills"])

    def test_no_per_function_tools_registered(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard
        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)
        model_config = {"name": "deepseek-chat", "api_key": "test_key", "base_url": "https://api.deepseek.com/v1", "gateway": "openai", "temperature": 0.7}
        agent = MainAgent(project_path, cm, sg, model_config)

        # per-function tools should NOT be registered
        assert "pandas_run" not in agent.tools
        assert "numpy_run" not in agent.tools
        assert "playwright_browser_automation" not in agent.tools

    def test_skills_discovered_and_listed(self):
        from aacode.tools.skills_tools import SkillsManager
        from pathlib import Path
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas", "numpy", "playwright"])

        skills_text = sm.get_skills_list_for_prompt()
        assert "pandas:" in skills_text
        assert "numpy:" in skills_text
        assert "playwright:" in skills_text


# ============================================================
# 3. run_skills 三种模式验证
# ============================================================

class TestRunSkillsModes:
    """验证 run_skills 的 __list__, __info__, execute 三种模式"""

    @pytest.mark.asyncio
    async def test_list_mode(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard
        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)
        model_config = {"name": "deepseek-chat", "api_key": "test_key", "base_url": "https://api.deepseek.com/v1", "gateway": "openai", "temperature": 0.7}
        agent = MainAgent(project_path, cm, sg, model_config)

        result = await agent._run_skills("__list__")
        assert "可用技能列表" in result or "Available" in result
        assert "pandas" in result
        assert "numpy" in result
        assert "playwright" in result

    @pytest.mark.asyncio
    async def test_info_mode(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard
        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)
        model_config = {"name": "deepseek-chat", "api_key": "test_key", "base_url": "https://api.deepseek.com/v1", "gateway": "openai", "temperature": 0.7}
        agent = MainAgent(project_path, cm, sg, model_config)

        result = await agent._run_skills("__info__", {"skill_name": "pandas"})
        assert "pandas" in result
        assert "Description" in result or "code" in result

    @pytest.mark.asyncio
    async def test_info_mode_missing_skill_name(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard
        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)
        model_config = {"name": "deepseek-chat", "api_key": "test_key", "base_url": "https://api.deepseek.com/v1", "gateway": "openai", "temperature": 0.7}
        agent = MainAgent(project_path, cm, sg, model_config)

        result = await agent._run_skills("__info__", {})
        assert "skill_name is required" in result

    @pytest.mark.asyncio
    async def test_execute_mode_pandas(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])
        sm._load_full_instruction("pandas")

        result = await sm.execute_skill("pandas", func_name="run", code="print('hello from pandas')")
        assert result["success"] is True


# ============================================================
# 4. System Prompt 占位符替换
# ============================================================

class TestPromptReplacement:
    """验证 {skills_list} 被正确替换"""

    @pytest.fixture
    def agent(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard

        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)

        model_config = {
            "name": "deepseek-chat",
            "api_key": "test_key",
            "base_url": "https://api.deepseek.com/v1",
            "gateway": "openai",
            "temperature": 0.7,
        }

        agent = MainAgent(project_path, cm, sg, model_config)
        return agent

    def test_placeholder_replaced(self, agent):
        """system_prompt 中不应残留 {skills_list} 占位符"""
        assert "{skills_list}" not in agent.system_prompt

    def test_run_skills_in_prompt(self, agent):
        """system_prompt 应包含 run_skills"""
        assert "run_skills" in agent.system_prompt

    def test_skills_description_in_prompt(self, agent):
        """system_prompt 应包含技能描述"""
        assert "pandas:" in agent.system_prompt or "Pandas" in agent.system_prompt

    def test_skills_section_contains_three_modes(self, agent):
        """Skills 部分应描述三种模式"""
        assert "__list__" in agent.system_prompt
        assert "__info__" in agent.system_prompt


# ============================================================
# 4. System Prompt 占位符替换
# ============================================================

class TestPromptReplacement:
    """验证 {skills_list} 被正确替换"""

    @pytest.fixture
    def agent(self):
        from aacode.core.main_agent import MainAgent
        from aacode.utils.context_manager import ContextManager
        from aacode.utils.safety import SafetyGuard

        project_path = Path("aacode")
        cm = ContextManager(project_path)
        sg = SafetyGuard(project_path)

        model_config = {
            "name": "deepseek-chat",
            "api_key": "test_key",
            "base_url": "https://api.deepseek.com/v1",
            "gateway": "openai",
            "temperature": 0.7,
        }

        agent = MainAgent(project_path, cm, sg, model_config)
        return agent

    def test_placeholder_replaced(self, agent):
        """system_prompt 中不应残留 {skills_list} 占位符"""
        assert "{skills_list}" not in agent.system_prompt

    def test_skills_description_in_prompt(self, agent):
        """system_prompt 应包含技能描述"""
        assert "run_skills" in agent.system_prompt

    def test_skills_section_contains_three_modes(self, agent):
        """Skills 部分应描述三种模式"""
        assert "__list__" in agent.system_prompt

    def test_user_skills_dir_in_prompt(self, agent):
        """system_prompt 应包含 user skills 目录路径"""
        assert "User skills directory:" in agent.system_prompt
        assert "com.aacode" in agent.system_prompt


# ============================================================
# 5. Tool Schema 验证
# ============================================================

class TestToolSchemas:
    """验证 run_skills schema 的存在和结构"""

    def test_run_skills_in_all_schemas(self):
        from aacode.utils.tool_schemas import get_all_schemas

        all_schemas = get_all_schemas()
        assert "run_skills" in all_schemas

    def test_run_skills_schema_has_correct_params(self):
        from aacode.utils.tool_schemas import ALL_SCHEMAS

        schema = ALL_SCHEMAS["run_skills"]
        param_names = [p.name for p in schema.parameters]
        assert "skill_name" in param_names
        assert "params" in param_names

    def test_no_old_skill_tools_in_all_schemas(self):
        from aacode.utils.tool_schemas import get_all_schemas

        all_schemas = get_all_schemas()
        # 旧工具已移除
        assert "list_skills" not in all_schemas
        assert "get_skill_info" not in all_schemas
        assert "run_skill" not in all_schemas
        # 新工具存在
        assert "run_skills" in all_schemas

    def test_skill_schemas_convertible_to_openai(self):
        from aacode.utils.tool_schemas import get_all_schemas
        from aacode.utils.tool_adapter import to_openai_tools

        all_schemas = get_all_schemas()
        tools = to_openai_tools(all_schemas)
        func_names = [t["function"]["name"] for t in tools]
        assert "run_skills" in func_names


# ============================================================
# 6. Skill 函数执行 (单元测试)
# ============================================================

class TestSkillExecution:
    """测试 pandas_run / numpy_run 的实际执行"""

    @pytest.mark.asyncio
    async def test_pandas_simple_code(self):
        """单行 pandas 代码执行"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        result = await sm.execute_skill("pandas", func_name="run", code="pd.DataFrame({'a': [1,2,3]}).describe()")
        assert result["success"] is True
        assert "dataframe_preview" in result or "stdout" in result

    @pytest.mark.asyncio
    async def test_pandas_multiline_with_braces(self):
        """多行代码（含 dict 花括号）—— 验证逐行 exec 修复"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        code = """df = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['alice', 'bob', 'charlie'],
    'score': [95, 87, 92]
})
df.describe()
"""
        result = await sm.execute_skill("pandas", func_name="run", code=code)
        assert result["success"] is True, f"多行代码执行失败: {result.get('error', '')}"
        assert "dataframe_preview" in result or "stdout" in result

    @pytest.mark.asyncio
    async def test_pandas_dataframe_to_excel(self):
        """创建 DataFrame 并验证"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        code = """df = pd.DataFrame({
    'col_a': list(range(5)),
    'col_b': [x * 2 for x in range(5)]
})
df
"""
        result = await sm.execute_skill("pandas", func_name="run", code=code)
        assert result["success"] is True
        preview = result.get("dataframe_preview", {})
        assert preview.get("shape") == [5, 2]

    @pytest.mark.asyncio
    async def test_numpy_simple_code(self):
        """numpy 单行代码"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["numpy"])

        result = await sm.execute_skill("numpy", func_name="run",
                                          code="np.array([1,2,3,4,5]).mean()")
        assert result["success"] is True
        assert "3.0" in str(result.get("result", ""))

    @pytest.mark.asyncio
    async def test_numpy_multiline_with_braces(self):
        """numpy 多行代码 + 花括号"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["numpy"])

        code = """data = {
    'a': np.array([1, 2, 3]),
    'b': np.array([4, 5, 6])
}
np.mean(list(data.values()))
"""
        result = await sm.execute_skill("numpy", func_name="run", code=code)
        assert result["success"] is True, f"numpy 多行代码失败: {result.get('error', '')}"

    @pytest.mark.asyncio
    async def test_pandas_imports_present(self):
        """验证 pandas 环境自动注入了 pd 和 np"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        result = await sm.execute_skill("pandas", func_name="run",
                                          code="pd.__version__")
        assert result["success"] is True

        result2 = await sm.execute_skill("pandas", func_name="run",
                                           code="np.__version__")
        assert result2["success"] is True

    @pytest.mark.asyncio
    async def test_pandas_syntax_error_graceful(self):
        """语法错误应优雅返回，不抛异常"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        result = await sm.execute_skill("pandas", func_name="run",
                                          code="this_is_not_valid_python @@@@")
        assert result["success"] is False
        assert "error" in result

    @pytest.mark.asyncio
    async def test_pandas_missing_brace_in_string(self):
        """字符串中花括号不闭合 + 代码中的 dict 不闭合"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        # 代码缺 } 是语法错误
        result = await sm.execute_skill("pandas", func_name="run",
                                          code="pd.DataFrame({'a': [1, 2, 3]")
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_pandas_empty_code(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["pandas"])

        result = await sm.execute_skill("pandas", func_name="run", code="# just a comment")
        assert result["success"] is True
        assert "(empty code)" in str(result) or "空代码" in str(result)


# ============================================================
# 7. 工具适配器: Skill Schema → OpenAI 格式
# ============================================================

class TestSkillToolAdapter:
    """skill schema 转换为 OpenAI 工具描述"""

    def test_run_skills_schema_to_openai_tools(self):
        from aacode.utils.tool_schemas import ALL_SCHEMAS
        from aacode.utils.tool_adapter import to_openai_tools

        tools = to_openai_tools({"run_skills": ALL_SCHEMAS["run_skills"]})
        assert len(tools) == 1
        func = tools[0]["function"]
        assert func["name"] == "run_skills"
        assert "skill_name" in func["parameters"]["properties"]
        assert "params" in func["parameters"]["properties"]


# ============================================================
# 8. 渐进式披露验证
# ============================================================

class TestProgressiveDisclosure:
    """渐进式披露：L1 签名暴露 / L2 文档按需加载"""

    def test_initial_discovery_does_not_load_full_md(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        discovered = sm.discover_skills(load_full_instructions=False)

        pandas = discovered["pandas"]
        assert pandas.full_instruction_loaded is False, \
            "初始发现不应加载完整 MD"

    def test_load_full_instruction_loads_md(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills(load_full_instructions=False)

        result = sm._load_full_instruction("pandas")
        assert result is True
        assert sm.loaded_skills["pandas"].full_instruction_loaded is True
        assert len(sm.loaded_skills["pandas"].full_md_content) > 0

    def test_metadata_available_without_full_md(self):
        """即使完整指令未加载，元数据仍然可用"""
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills(load_full_instructions=False)

        info = sm.loaded_skills.get("pandas")
        assert info is not None
        assert info.description != ""
        assert info.metadata_loaded is True
        assert info.full_instruction_loaded is False


# ============================================================
# 9. Multi-function skill (playwright) 验证
# ============================================================

class TestMultiFunctionSkill:
    """多函数 skill 的正确性"""

    def test_playwright_schema_has_sub_functions(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm._load_full_instruction("playwright")

        info = sm.loaded_skills["playwright"]
        assert len(info.functions) >= 3
        assert "browser_automation" in info.functions
        assert "take_screenshot" in info.functions
        assert "scrape_dynamic_page" in info.functions

    def test_playwright_browser_automation_has_url_param(self):
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm._load_full_instruction("playwright")

        info = sm.loaded_skills["playwright"]
        ba_params = info.functions["browser_automation"]["parameters"]
        assert "url" in ba_params
        assert ba_params["url"]["required"] is True

    def test_playwright_execute_unknown_function(self):
        import asyncio
        from aacode.tools.skills_tools import SkillsManager
        sm = SkillsManager(Path("aacode"))
        sm.discover_skills()
        sm.enable_skills(["playwright"])

        async def run():
            return await sm.execute_skill("playwright", func_name="nonexistent_func")

        result = asyncio.run(run())
        assert result["success"] is False
