"""SkillConfig 单元测试"""

import os
import tempfile

import pytest
import yaml

from agentkit.core.exceptions import ConfigValidationError
from agentkit.skills.base import IntentConfig, QualityGateConfig, SkillConfig, Skill


# ── IntentConfig 测试 ──────────────────────────────────────


class TestIntentConfig:
    """IntentConfig 数据类测试"""

    def test_default_values(self):
        intent = IntentConfig()
        assert intent.keywords == []
        assert intent.description == ""
        assert intent.examples == []

    def test_from_dict_with_all_fields(self):
        data = {
            "keywords": ["生成", "写作"],
            "description": "内容生成意图",
            "examples": ["帮我写一篇文章", "生成一段文案"],
        }
        intent = IntentConfig(**data)
        assert intent.keywords == ["生成", "写作"]
        assert intent.description == "内容生成意图"
        assert intent.examples == ["帮我写一篇文章", "生成一段文案"]

    def test_empty_keywords_is_valid(self):
        intent = IntentConfig(keywords=[])
        assert intent.keywords == []


# ── QualityGateConfig 测试 ─────────────────────────────────


class TestQualityGateConfig:
    """QualityGateConfig 数据类测试"""

    def test_default_values(self):
        gate = QualityGateConfig()
        assert gate.required_fields == []
        assert gate.min_word_count == 0
        assert gate.max_retries == 0
        assert gate.custom_validator is None

    def test_from_dict_with_all_fields(self):
        data = {
            "required_fields": ["title", "body"],
            "min_word_count": 100,
            "max_retries": 3,
            "custom_validator": "validators.check_quality",
        }
        gate = QualityGateConfig(**data)
        assert gate.required_fields == ["title", "body"]
        assert gate.min_word_count == 100
        assert gate.max_retries == 3
        assert gate.custom_validator == "validators.check_quality"

    def test_max_retries_defaults_to_zero(self):
        gate = QualityGateConfig()
        assert gate.max_retries == 0


# ── SkillConfig 测试 ───────────────────────────────────────


class TestSkillConfig:
    """SkillConfig 继承 AgentConfig 并扩展 v2 字段"""

    def test_from_dict_with_intent_and_quality_gate(self):
        data = {
            "name": "content_gen",
            "agent_type": "content_generation",
            "task_mode": "llm_generate",
            "prompt": {"identity": "你是内容生成助手"},
            "intent": {
                "keywords": ["生成", "写作"],
                "description": "内容生成意图",
                "examples": ["帮我写文章"],
            },
            "quality_gate": {
                "required_fields": ["title", "body"],
                "min_word_count": 100,
                "max_retries": 3,
            },
            "execution_mode": "react",
            "max_steps": 10,
        }
        config = SkillConfig.from_dict(data)
        assert config.name == "content_gen"
        assert config.intent.keywords == ["生成", "写作"]
        assert config.intent.description == "内容生成意图"
        assert config.quality_gate.required_fields == ["title", "body"]
        assert config.quality_gate.max_retries == 3
        assert config.execution_mode == "react"
        assert config.max_steps == 10

    def test_from_old_agent_config_dict_auto_fills_defaults(self):
        """旧 AgentConfig 字典（无 intent/quality_gate）应自动填充默认值"""
        data = {
            "name": "geo_writer",
            "agent_type": "geo_writing",
            "task_mode": "llm_generate",
            "prompt": {"identity": "你是 GEO 写作助手"},
        }
        config = SkillConfig.from_dict(data)
        assert config.name == "geo_writer"
        assert isinstance(config.intent, IntentConfig)
        assert config.intent.keywords == []
        assert config.intent.description == ""
        assert config.intent.examples == []
        assert isinstance(config.quality_gate, QualityGateConfig)
        assert config.quality_gate.required_fields == []
        assert config.quality_gate.max_retries == 0

    def test_execution_mode_defaults_to_react(self):
        data = {
            "name": "test_skill",
            "agent_type": "test",
            "task_mode": "llm_generate",
            "prompt": {"identity": "test"},
        }
        config = SkillConfig.from_dict(data)
        assert config.execution_mode == "react"

    def test_max_steps_defaults_to_five(self):
        data = {
            "name": "test_skill",
            "agent_type": "test",
            "task_mode": "llm_generate",
            "prompt": {"identity": "test"},
        }
        config = SkillConfig.from_dict(data)
        assert config.max_steps == 5

    def test_backward_compat_old_yaml_without_intent(self):
        """旧 YAML 无 intent 字段 → intent 默认为空 IntentConfig"""
        yaml_content = yaml.dump({
            "name": "legacy_skill",
            "agent_type": "legacy",
            "task_mode": "llm_generate",
            "prompt": {"identity": "旧技能"},
        })
        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".yaml", delete=False, encoding="utf-8"
        ) as f:
            f.write(yaml_content)
            path = f.name
        try:
            config = SkillConfig.from_yaml(path)
            assert config.name == "legacy_skill"
            assert isinstance(config.intent, IntentConfig)
            assert config.intent.keywords == []
            assert isinstance(config.quality_gate, QualityGateConfig)
            assert config.quality_gate.max_retries == 0
            assert config.execution_mode == "react"
        finally:
            os.unlink(path)

    def test_from_yaml_loads_correctly(self):
        yaml_content = yaml.dump({
            "name": "yaml_skill",
            "agent_type": "yaml_type",
            "task_mode": "llm_generate",
            "prompt": {"identity": "YAML 技能"},
            "intent": {"keywords": ["yaml"], "description": "YAML 加载测试"},
            "quality_gate": {"required_fields": ["result"], "max_retries": 2},
            "execution_mode": "direct",
            "max_steps": 3,
        })
        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".yaml", delete=False, encoding="utf-8"
        ) as f:
            f.write(yaml_content)
            path = f.name
        try:
            config = SkillConfig.from_yaml(path)
            assert config.name == "yaml_skill"
            assert config.intent.keywords == ["yaml"]
            assert config.quality_gate.max_retries == 2
            assert config.execution_mode == "direct"
            assert config.max_steps == 3
        finally:
            os.unlink(path)

    def test_to_dict_includes_v2_fields(self):
        data = {
            "name": "dict_skill",
            "agent_type": "dict_type",
            "task_mode": "llm_generate",
            "prompt": {"identity": "字典技能"},
            "intent": {"keywords": ["dict"]},
            "quality_gate": {"required_fields": ["output"]},
            "execution_mode": "custom",
            "max_steps": 7,
        }
        config = SkillConfig.from_dict(data)
        result = config.to_dict()
        assert "intent" in result
        assert result["intent"]["keywords"] == ["dict"]
        assert "quality_gate" in result
        assert result["quality_gate"]["required_fields"] == ["output"]
        assert result["execution_mode"] == "custom"
        assert result["max_steps"] == 7

    def test_to_dict_includes_v2_defaults_when_not_provided(self):
        data = {
            "name": "minimal_skill",
            "agent_type": "minimal",
            "task_mode": "llm_generate",
            "prompt": {"identity": "最小技能"},
        }
        config = SkillConfig.from_dict(data)
        result = config.to_dict()
        assert "intent" in result
        assert result["intent"]["keywords"] == []
        assert "quality_gate" in result
        assert result["quality_gate"]["max_retries"] == 0
        assert result["execution_mode"] == "react"
        assert result["max_steps"] == 5

    def test_invalid_execution_mode_raises_config_validation_error(self):
        data = {
            "name": "bad_mode",
            "agent_type": "bad",
            "task_mode": "llm_generate",
            "prompt": {"identity": "坏模式"},
            "execution_mode": "invalid_mode",
        }
        with pytest.raises(ConfigValidationError):
            SkillConfig.from_dict(data)

    def test_direct_execution_mode(self):
        data = {
            "name": "direct_skill",
            "agent_type": "direct",
            "task_mode": "tool_call",
            "tools": ["some_tool"],
            "execution_mode": "direct",
        }
        config = SkillConfig.from_dict(data)
        assert config.execution_mode == "direct"

    def test_custom_execution_mode(self):
        data = {
            "name": "custom_skill",
            "agent_type": "custom",
            "task_mode": "custom",
            "custom_handler": "handlers.custom",
            "execution_mode": "custom",
        }
        config = SkillConfig.from_dict(data)
        assert config.execution_mode == "custom"


# ── Skill 测试 ─────────────────────────────────────────────


class TestSkill:
    """Skill 类测试"""

    def _make_config(self, name: str = "test_skill") -> SkillConfig:
        return SkillConfig.from_dict({
            "name": name,
            "agent_type": "test",
            "task_mode": "llm_generate",
            "prompt": {"identity": "测试技能"},
        })

    def test_skill_name_property(self):
        config = self._make_config("my_skill")
        skill = Skill(config)
        assert skill.name == "my_skill"

    def test_skill_config_property(self):
        config = self._make_config()
        skill = Skill(config)
        assert skill.config is config

    def test_skill_tools_default_empty(self):
        config = self._make_config()
        skill = Skill(config)
        assert skill.tools == []

    def test_skill_bind_tool(self):
        from agentkit.tools.base import Tool

        class DummyTool(Tool):
            async def execute(self, **kwargs):
                return {}

        config = self._make_config()
        skill = Skill(config)
        tool = DummyTool(name="t1", description="test tool")
        skill.bind_tool(tool)
        assert len(skill.tools) == 1
        assert skill.tools[0].name == "t1"

    def test_skill_unbind_tool(self):
        from agentkit.tools.base import Tool

        class DummyTool(Tool):
            async def execute(self, **kwargs):
                return {}

        config = self._make_config()
        skill = Skill(config)
        tool = DummyTool(name="t1", description="test tool")
        skill.bind_tool(tool)
        skill.unbind_tool("t1")
        assert skill.tools == []

    def test_skill_unbind_nonexistent_tool_no_error(self):
        config = self._make_config()
        skill = Skill(config)
        skill.unbind_tool("nonexistent")  # 不应抛异常
        assert skill.tools == []

    def test_skill_to_dict(self):
        config = self._make_config()
        skill = Skill(config)
        d = skill.to_dict()
        assert "config" in d
        assert d["config"]["name"] == "test_skill"
        assert "tools" in d
        assert d["tools"] == []

    def test_skill_with_tools_in_constructor(self):
        from agentkit.tools.base import Tool

        class DummyTool(Tool):
            async def execute(self, **kwargs):
                return {}

        config = self._make_config()
        tool = DummyTool(name="t1", description="test tool")
        skill = Skill(config, tools=[tool])
        assert len(skill.tools) == 1
