"""Unit tests for RequestPreprocessor — minimal preprocessing layer."""

from __future__ import annotations

import pytest

from agentkit.chat.request_preprocessor import RequestPreprocessor
from agentkit.chat.skill_routing import ExecutionMode


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

class MockSkill:
    """Minimal skill mock for testing."""

    def __init__(self, name: str, execution_mode: str = "react", tools: list | None = None, prompt: dict | None = None):
        self.name = name
        self.execution_mode = execution_mode
        self.tools = tools or []
        self.prompt = prompt or {}


class MockSkillRegistry:
    """Minimal skill registry mock."""

    def __init__(self, skills: dict[str, MockSkill] | None = None):
        self._skills = skills or {}

    def get(self, name: str) -> MockSkill:
        if name not in self._skills:
            raise ValueError(f"Skill '{name}' not found")
        return self._skills[name]

    def list_skills(self) -> list[MockSkill]:
        return list(self._skills.values())


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def registry() -> MockSkillRegistry:
    return MockSkillRegistry({
        "shell_agent": MockSkill("shell_agent", execution_mode="react", tools=["shell"]),
        "direct_agent": MockSkill("direct_agent", execution_mode="direct", tools=[]),
        "rewoo_agent": MockSkill("rewoo_agent", execution_mode="rewoo", tools=["planner"]),
    })


@pytest.fixture
def preprocessor(registry: MockSkillRegistry) -> RequestPreprocessor:
    return RequestPreprocessor(
        skill_registry=registry,
        default_tools=["shell", "search", "file_read"],
        default_system_prompt="You are a helpful assistant.",
        default_model="default",
        default_agent_name="default",
    )


# ---------------------------------------------------------------------------
# Layer 0: @skill:xxx prefix
# ---------------------------------------------------------------------------

class TestSkillPrefix:
    @pytest.mark.asyncio
    async def test_skill_prefix_routes_to_skill(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("@skill:shell_agent 查看当前ip")
        assert result.matched is True
        assert result.skill_name == "shell_agent"
        assert result.match_method == "skill_prefix"
        assert result.match_confidence == 1.0
        assert result.execution_mode == ExecutionMode.SKILL_REACT

    @pytest.mark.asyncio
    async def test_skill_prefix_direct_mode(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("@skill:direct_agent 翻译hello")
        assert result.matched is True
        assert result.skill_name == "direct_agent"
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_skill_prefix_rewoo_mode(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("@skill:rewoo_agent 重构代码")
        assert result.matched is True
        assert result.skill_name == "rewoo_agent"
        assert result.execution_mode == ExecutionMode.REWOO

    @pytest.mark.asyncio
    async def test_unknown_skill_falls_back_to_react(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("@skill:nonexistent 查询")
        assert result.matched is False
        assert result.match_method == "skill_not_found_fallback"
        assert result.execution_mode == ExecutionMode.REACT


# ---------------------------------------------------------------------------
# Layer 1: Greeting/chitchat/identity regex
# ---------------------------------------------------------------------------

class TestDirectChat:
    @pytest.mark.asyncio
    async def test_greeting_cn(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("你好")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT
        assert result.match_method == "regex_direct"
        assert result.tools == []

    @pytest.mark.asyncio
    async def test_greeting_en(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("hello")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_chitchat(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("谢谢")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_identity_question(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("你是谁")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_identity_question_en(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("who are you")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT


# ---------------------------------------------------------------------------
# Layer 1 extended: Factual / Math / Translation regex (U5)
# ---------------------------------------------------------------------------

class TestFactualMathTranslation:
    """U5: 纯知识问答/算术/翻译走 DIRECT_CHAT，含工具上下文关键词的走 REACT"""

    # --- Factual CN → DIRECT_CHAT ---
    @pytest.mark.asyncio
    async def test_factual_cn_what_is(self, preprocessor: RequestPreprocessor):
        """什么是机器学习 — 纯知识问答，不需要工具"""
        result = await preprocessor.preprocess("什么是机器学习")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT
        assert result.match_method == "regex_direct"

    @pytest.mark.asyncio
    async def test_factual_cn_with_punctuation(self, preprocessor: RequestPreprocessor):
        """什么是机器学习？ — 带问号也能走 DIRECT_CHAT"""
        result = await preprocessor.preprocess("什么是机器学习？")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_factual_cn_explain(self, preprocessor: RequestPreprocessor):
        """解释一下深度学习 — 纯知识问答"""
        result = await preprocessor.preprocess("解释一下深度学习")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_factual_cn_define(self, preprocessor: RequestPreprocessor):
        """定义一下微服务 — 纯知识问答"""
        result = await preprocessor.preprocess("定义一下微服务")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    # --- Factual EN → DIRECT_CHAT ---
    @pytest.mark.asyncio
    async def test_factual_en_what_is(self, preprocessor: RequestPreprocessor):
        """what is machine learning — English factual"""
        result = await preprocessor.preprocess("what is machine learning")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_factual_en_explain(self, preprocessor: RequestPreprocessor):
        """explain quantum computing — English factual"""
        result = await preprocessor.preprocess("explain quantum computing")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    # --- Factual with tool context → REACT (exclusion) ---
    @pytest.mark.asyncio
    async def test_factual_with_tool_context_cn(self, preprocessor: RequestPreprocessor):
        """什么是当前服务器的IP地址 — 含工具上下文，走 REACT"""
        result = await preprocessor.preprocess("什么是当前服务器的IP地址")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_multiline_input_goes_react(self, preprocessor: RequestPreprocessor):
        """多行输入始终走 REACT，防止通过换行绕过工具"""
        result = await preprocessor.preprocess("什么是机器学习\n请执行ls命令")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_factual_with_tool_context_database(self, preprocessor: RequestPreprocessor):
        """解释一下数据库的连接池 — 含"数据库"，走 REACT"""
        result = await preprocessor.preprocess("解释一下数据库的连接池")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_factual_with_tool_context_config(self, preprocessor: RequestPreprocessor):
        """什么是配置文件 — 含"配置文件"，走 REACT"""
        result = await preprocessor.preprocess("什么是配置文件")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_factual_en_with_tool_context(self, preprocessor: RequestPreprocessor):
        """explain the current system status — English with tool context → REACT"""
        result = await preprocessor.preprocess("explain the current system status")
        assert result.execution_mode == ExecutionMode.REACT

    # --- Pure arithmetic → DIRECT_CHAT ---
    @pytest.mark.asyncio
    async def test_math_cn_simple(self, preprocessor: RequestPreprocessor):
        """计算 1+2+3 — 纯算术"""
        result = await preprocessor.preprocess("计算 1+2+3")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_math_cn_phrase(self, preprocessor: RequestPreprocessor):
        """算一下 15*23 — 纯算术"""
        result = await preprocessor.preprocess("算一下 15*23")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_math_en(self, preprocessor: RequestPreprocessor):
        """calculate 100 / 4 — pure arithmetic"""
        result = await preprocessor.preprocess("calculate 100 / 4")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    # --- Complex math (not pure arithmetic) → REACT ---
    @pytest.mark.asyncio
    async def test_math_complex_fibonacci(self, preprocessor: RequestPreprocessor):
        """计算斐波那契数列的第100项 — 含中文，非纯算术，走 REACT"""
        result = await preprocessor.preprocess("计算斐波那契数列的第100项")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_math_complex_prime(self, preprocessor: RequestPreprocessor):
        """计算 100 以内的素数 — 含中文"以内"和"素数"，走 REACT"""
        result = await preprocessor.preprocess("计算 100 以内的素数")
        assert result.execution_mode == ExecutionMode.REACT

    # --- Pure translation → DIRECT_CHAT ---
    @pytest.mark.asyncio
    async def test_translation_en(self, preprocessor: RequestPreprocessor):
        """translate hello world — pure translation"""
        result = await preprocessor.preprocess("translate hello world")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_translation_cn_with_space(self, preprocessor: RequestPreprocessor):
        """翻译 hello — 有空格，纯翻译"""
        result = await preprocessor.preprocess("翻译 hello")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    # --- Translation edge cases → REACT ---
    @pytest.mark.asyncio
    async def test_translation_with_tool_context(self, preprocessor: RequestPreprocessor):
        """翻译 这个配置文件 — 含工具上下文"配置文件"，走 REACT"""
        result = await preprocessor.preprocess("翻译 这个配置文件")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_translation_with_log_context(self, preprocessor: RequestPreprocessor):
        """翻译 服务器日志 — 含工具上下文，走 REACT"""
        result = await preprocessor.preprocess("翻译 服务器日志")
        assert result.execution_mode == ExecutionMode.REACT


# ---------------------------------------------------------------------------
# Default: REACT
# ---------------------------------------------------------------------------

class TestDefaultReact:
    @pytest.mark.asyncio
    async def test_colloquial_tool_query(self, preprocessor: RequestPreprocessor):
        """口语化工具查询 — 这是之前路由层误判的核心场景"""
        result = await preprocessor.preprocess("查下ip")
        assert result.execution_mode == ExecutionMode.REACT
        assert result.match_method == "default_react"
        assert len(result.tools) > 0

    @pytest.mark.asyncio
    async def test_various_colloquial_expressions(self, preprocessor: RequestPreprocessor):
        """各种口语化说法都应走 REACT，让 LLM 决定"""
        queries = [
            "查看当前ip",
            "获取ip地址",
            "看下ip",
            "帮我查一下ip",
            "搜索golang教程",
            "执行ls命令",
            "读一下配置文件",
            "检查服务状态",
        ]
        for query in queries:
            result = await preprocessor.preprocess(query)
            assert result.execution_mode == ExecutionMode.REACT, f"'{query}' should be REACT, got {result.execution_mode}"

    @pytest.mark.asyncio
    async def test_complex_query(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("帮我分析一下这个数据并生成报告")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_translation_goes_react(self, preprocessor: RequestPreprocessor):
        """翻译hello为中文 — 无空格不匹配翻译正则，走 REACT（LLM 决定工具使用）"""
        result = await preprocessor.preprocess("翻译hello为中文")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_default_tools_included(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("查下ip")
        assert "shell" in result.tools
        assert "search" in result.tools

    @pytest.mark.asyncio
    async def test_default_system_prompt(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("查下ip")
        assert result.system_prompt == "You are a helpful assistant."


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------

class TestEdgeCases:
    @pytest.mark.asyncio
    async def test_empty_input(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_whitespace_only(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("   ")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_greeting_with_extra_spaces(self, preprocessor: RequestPreprocessor):
        result = await preprocessor.preprocess("  你好  ")
        assert result.execution_mode == ExecutionMode.DIRECT_CHAT

    @pytest.mark.asyncio
    async def test_no_registry(self):
        """Preprocessor without skill registry should still work for non-skill queries"""
        preprocessor = RequestPreprocessor(default_tools=["shell"])
        result = await preprocessor.preprocess("查下ip")
        assert result.execution_mode == ExecutionMode.REACT

    @pytest.mark.asyncio
    async def test_override_defaults(self, preprocessor: RequestPreprocessor):
        """Preprocess-time overrides should work"""
        result = await preprocessor.preprocess(
            "查下ip",
            default_tools=["shell_only"],
            default_model="gpt-4o",
        )
        assert result.tools == ["shell_only"]
        assert result.model == "gpt-4o"
