"""Unit tests for ExpertTeamRouter and skill routing utilities."""

from __future__ import annotations

import pytest

from agentkit.chat.skill_routing import (
    ExecutionMode,
    SkillRoutingResult,
    build_skill_system_prompt,
)
from agentkit.experts.config import ExpertConfig, ExpertTemplate
from agentkit.experts.registry import ExpertTemplateRegistry
from agentkit.experts.router import ExpertTeamRouter
from agentkit.skills.base import Skill, SkillConfig
from agentkit.skills.registry import SkillRegistry
from agentkit.skills.skill_detail import SkillDetailTool


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_team_router_with_templates() -> ExpertTeamRouter:
    """Create an ExpertTeamRouter with sample templates."""
    registry = ExpertTemplateRegistry()
    for name in ("analyst", "strategist", "reviewer"):
        config = ExpertConfig(
            name=name,
            agent_type="expert",
            persona=f"Expert in {name}",
            thinking_style="analytical",
            bound_skills=[],
            is_lead=(name == "analyst"),
            task_mode="llm_generate",
            prompt={"identity": f"Expert in {name}"},
        )
        template = ExpertTemplate(
            name=name,
            config=config,
            description=f"Handles {name} tasks",
        )
        registry.register(template)
    return ExpertTeamRouter(template_registry=registry)


def _make_team_router_empty() -> ExpertTeamRouter:
    """Create an ExpertTeamRouter with no templates."""
    return ExpertTeamRouter(template_registry=ExpertTemplateRegistry())


# ---------------------------------------------------------------------------
# Tests: ExpertTeamRouter.can_handle()
# ---------------------------------------------------------------------------


class TestExpertTeamRouterCanHandle:
    def test_can_handle_with_templates(self) -> None:
        router = _make_team_router_with_templates()
        assert router.can_handle("analyze this data") is True

    def test_can_handle_no_templates(self) -> None:
        router = _make_team_router_empty()
        assert router.can_handle("analyze this data") is False

    def test_can_handle_name_match(self) -> None:
        router = _make_team_router_with_templates()
        assert router.can_handle("I need a strategist for this") is True

    def test_can_handle_description_match(self) -> None:
        router = _make_team_router_with_templates()
        assert router.can_handle("handles review tasks") is True


# ---------------------------------------------------------------------------
# Tests: ExpertTeamRouter.resolve()
# ---------------------------------------------------------------------------


class TestExpertTeamRouterResolve:
    def test_explicit_team_prefix(self) -> None:
        router = _make_team_router_with_templates()
        result = router.resolve("@team:analyst,strategist analyze the market")
        assert result.team_mode is True
        assert result.match_method == "explicit_team"
        assert "analyst" in result.specified_experts
        assert "strategist" in result.specified_experts

    def test_no_team_without_prefix(self) -> None:
        router = _make_team_router_with_templates()
        result = router.resolve("simple question")
        assert result.team_mode is False


# ---------------------------------------------------------------------------
# Tests: SkillRoutingResult data structure
# ---------------------------------------------------------------------------


class TestSkillRoutingResult:
    def test_default_execution_mode(self) -> None:
        result = SkillRoutingResult(
            clean_content="test",
            matched=False,
            match_method="default_react",
            match_confidence=0.8,
            agent_name="default",
            model="default",
            execution_mode=ExecutionMode.REACT,
        )
        assert result.execution_mode == ExecutionMode.REACT

    def test_direct_chat_mode(self) -> None:
        result = SkillRoutingResult(
            clean_content="hello",
            matched=False,
            match_method="regex_direct",
            match_confidence=1.0,
            agent_name="default",
            model="default",
            execution_mode=ExecutionMode.DIRECT_CHAT,
        )
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT


# ---------------------------------------------------------------------------
# U5: Progressive Skill Loading Tests
# ---------------------------------------------------------------------------


def _make_skill_config(
    name: str = "research",
    description: str = "Research skill for gathering information",
    disclosure_level: int = 1,
    preconditions: list[str] | None = None,
) -> SkillConfig:
    """Create a SkillConfig for testing progressive loading."""
    return SkillConfig(
        name=name,
        agent_type="skill",
        description=description,
        task_mode="llm_generate",
        execution_mode="react",
        prompt={
            "identity": f"You are a {name} expert.",
            "context": "Use this skill when research is needed.",
            "instructions": "Step 1: gather data. Step 2: analyze.",
            "constraints": "Do not hallucinate.",
            "output_format": "Markdown report.",
        },
        disclosure_level=disclosure_level,
        preconditions=preconditions,
    )


def _make_skill_registry_with_skills(*configs: SkillConfig) -> SkillRegistry:
    """Create a SkillRegistry with the given skill configs registered."""
    registry = SkillRegistry()
    for config in configs:
        registry.register(Skill(config=config, tools=[]))
    return registry


class TestProgressiveSkillLoading:
    """U5: 渐进式技能加载 — disclosure_level 控制概要 vs 全量注入。"""

    def test_level_0_summary_only_name_and_description(self) -> None:
        """disclosure_level=0 时，system_prompt 只含 skill name + description。"""
        config = _make_skill_config(
            name="research",
            description="Research skill for gathering information",
            disclosure_level=0,
        )
        prompt = build_skill_system_prompt(config)

        assert prompt is not None
        assert "## Skill: research" in prompt
        assert "Research skill for gathering information" in prompt
        # Should NOT contain full prompt sections
        assert "You are a research expert" not in prompt
        assert "Step 1: gather data" not in prompt
        assert "Do not hallucinate" not in prompt
        # Should hint at skill_detail tool
        assert "skill_detail" in prompt

    def test_level_0_with_preconditions_includes_guard(self) -> None:
        """disclosure_level=0 时，前置条件仍然注入（安全守卫）。"""
        config = _make_skill_config(
            name="dangerous_op",
            description="A dangerous operation skill",
            disclosure_level=0,
            preconditions=["User must confirm the operation", "API key must be set"],
        )
        prompt = build_skill_system_prompt(config)

        assert prompt is not None
        assert "## Skill: dangerous_op" in prompt
        assert "## Activation Preconditions" in prompt
        assert "User must confirm the operation" in prompt
        assert "API key must be set" in prompt

    def test_level_1_full_prompt_backward_compatible(self) -> None:
        """disclosure_level=1（默认）时，行为与现状一致（全量加载）。"""
        config = _make_skill_config(
            name="research",
            description="Research skill",
            disclosure_level=1,
        )
        prompt = build_skill_system_prompt(config)

        assert prompt is not None
        assert "You are a research expert." in prompt
        assert "Step 1: gather data. Step 2: analyze." in prompt
        assert "Do not hallucinate." in prompt
        assert "Markdown report." in prompt
        # Should NOT contain the skill_detail hint
        assert "skill_detail" not in prompt

    def test_level_0_no_description_still_works(self) -> None:
        """disclosure_level=0 且无 description 时，仍能生成有效 prompt。"""
        config = _make_skill_config(
            name="minimal",
            description="",
            disclosure_level=0,
        )
        prompt = build_skill_system_prompt(config)

        assert prompt is not None
        assert "## Skill: minimal" in prompt
        assert "skill_detail" in prompt


class TestSkillDetailTool:
    """U5: SkillDetailTool — 按需加载完整 skill instructions。"""

    @pytest.mark.asyncio
    async def test_exact_name_match_returns_full_instructions(self) -> None:
        """精确名称匹配 → 返回完整 instructions。"""
        config = _make_skill_config(name="research", disclosure_level=0)
        registry = _make_skill_registry_with_skills(config)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="research")

        assert result["name"] == "research"
        assert result["description"] == "Research skill for gathering information"
        assert "You are a research expert." in result["full_instructions"]
        assert "Step 1: gather data" in result["full_instructions"]
        assert "Do not hallucinate." in result["full_instructions"]

    @pytest.mark.asyncio
    async def test_keyword_search_returns_matching_skill(self) -> None:
        """关键词搜索 → 返回匹配的 skill 完整内容。"""
        config1 = _make_skill_config(name="research", description="Research skill")
        config2 = _make_skill_config(
            name="code_review",
            description="Review code quality",
        )
        registry = _make_skill_registry_with_skills(config1, config2)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="review")

        assert result["name"] == "code_review"
        assert "Review code quality" in result["description"]

    @pytest.mark.asyncio
    async def test_no_match_returns_message(self) -> None:
        """无匹配 → 返回 'No skills matched' 提示。"""
        config = _make_skill_config(name="research")
        registry = _make_skill_registry_with_skills(config)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="nonexistent_skill")

        assert result["count"] == 0
        assert result["results"] == []
        assert "No skills matched" in result["message"]

    @pytest.mark.asyncio
    async def test_empty_query_returns_error(self) -> None:
        """空 query → 返回错误。"""
        config = _make_skill_config(name="research")
        registry = _make_skill_registry_with_skills(config)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="")

        assert "error" in result
        assert "query parameter is required" in result["error"]

    @pytest.mark.asyncio
    async def test_includes_preconditions_in_full_instructions(self) -> None:
        """完整 instructions 中包含前置条件（安全守卫）。"""
        config = _make_skill_config(
            name="dangerous_op",
            preconditions=["User must confirm", "API key set"],
            disclosure_level=0,
        )
        registry = _make_skill_registry_with_skills(config)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="dangerous_op")

        assert "### Activation Preconditions" in result["full_instructions"]
        assert "User must confirm" in result["full_instructions"]
        assert "API key set" in result["full_instructions"]

    @pytest.mark.asyncio
    async def test_case_insensitive_keyword_search(self) -> None:
        """关键词搜索大小写不敏感。"""
        config = _make_skill_config(name="Research", description="Research skill")
        registry = _make_skill_registry_with_skills(config)

        tool = SkillDetailTool(skill_registry=registry)
        result = await tool.execute(query="RESEARCH")

        assert result["name"] == "Research"


class TestResolveSkillRoutingProgressive:
    """U5: resolve_skill_routing 在 disclosure_level=0 时注入 skill_detail 工具。"""

    @pytest.mark.asyncio
    async def test_level_0_adds_skill_detail_tool(self) -> None:
        """disclosure_level=0 时，routing 结果包含 skill_detail 工具。"""
        from agentkit.chat.skill_routing import resolve_skill_routing

        config = _make_skill_config(name="research", disclosure_level=0)
        registry = _make_skill_registry_with_skills(config)

        result = await resolve_skill_routing(
            content="@skill:research analyze the data",
            skill_registry=registry,
            default_tools=[],
            default_system_prompt="default",
        )

        assert result.matched is True
        assert result.skill_name == "research"
        tool_names = [t.name for t in result.tools]
        assert "skill_detail" in tool_names

    @pytest.mark.asyncio
    async def test_level_1_does_not_add_skill_detail_tool(self) -> None:
        """disclosure_level=1（默认）时，不注入 skill_detail 工具（向后兼容）。"""
        from agentkit.chat.skill_routing import resolve_skill_routing

        config = _make_skill_config(name="research", disclosure_level=1)
        registry = _make_skill_registry_with_skills(config)

        result = await resolve_skill_routing(
            content="@skill:research analyze the data",
            skill_registry=registry,
            default_tools=[],
            default_system_prompt="default",
        )

        assert result.matched is True
        tool_names = [t.name for t in result.tools]
        assert "skill_detail" not in tool_names

    @pytest.mark.asyncio
    async def test_level_0_system_prompt_is_summary(self) -> None:
        """disclosure_level=0 时，system_prompt 是概要模式。"""
        from agentkit.chat.skill_routing import resolve_skill_routing

        config = _make_skill_config(name="research", disclosure_level=0)
        registry = _make_skill_registry_with_skills(config)

        result = await resolve_skill_routing(
            content="@skill:research analyze the data",
            skill_registry=registry,
            default_tools=[],
            default_system_prompt="default",
        )

        assert result.system_prompt is not None
        assert "## Skill: research" in result.system_prompt
        # Full instructions should NOT be in the system prompt
        assert "You are a research expert." not in result.system_prompt


class TestRequestPreprocessorProgressive:
    """U5: RequestPreprocessor._resolve_explicit_skill 在 disclosure_level=0 时注入 skill_detail。"""

    @pytest.mark.asyncio
    async def test_preprocessor_level_0_adds_skill_detail(self) -> None:
        """RequestPreprocessor 在 disclosure_level=0 时注入 skill_detail 工具。"""
        from agentkit.chat.request_preprocessor import RequestPreprocessor

        config = _make_skill_config(name="research", disclosure_level=0)
        registry = _make_skill_registry_with_skills(config)

        preprocessor = RequestPreprocessor()
        result = await preprocessor.preprocess(
            content="@skill:research analyze the data",
            skill_registry=registry,
            default_tools=[],
            default_system_prompt="default",
        )

        assert result.matched is True
        assert result.skill_name == "research"
        tool_names = [t.name for t in result.tools]
        assert "skill_detail" in tool_names

    @pytest.mark.asyncio
    async def test_preprocessor_level_1_no_skill_detail(self) -> None:
        """RequestPreprocessor 在 disclosure_level=1 时不注入 skill_detail（向后兼容）。"""
        from agentkit.chat.request_preprocessor import RequestPreprocessor

        config = _make_skill_config(name="research", disclosure_level=1)
        registry = _make_skill_registry_with_skills(config)

        preprocessor = RequestPreprocessor()
        result = await preprocessor.preprocess(
            content="@skill:research analyze the data",
            skill_registry=registry,
            default_tools=[],
            default_system_prompt="default",
        )

        assert result.matched is True
        tool_names = [t.name for t in result.tools]
        assert "skill_detail" not in tool_names
