"""Router 单元测试 — 三种路由模式 + 校验 + fallback"""

import pytest
from unittest.mock import MagicMock, patch

from src.daemon.router import (
    AgentRouter, AgentProfile, LLMDriver, RouteDecision, KNOWN_CAPABILITIES,
)


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

@pytest.fixture
def profiles():
    return {
        "zhangfei-dev": AgentProfile(
            agent_id="zhangfei-dev",
            capabilities=["coding", "implementation", "scripting"],
            can_review=False, max_concurrent=1,
        ),
        "simayi-challenger": AgentProfile(
            agent_id="simayi-challenger",
            capabilities=["review", "quality_check", "debate"],
            can_review=True, max_concurrent=2,
        ),
        "pangtong-fujunshi": AgentProfile(
            agent_id="pangtong-fujunshi",
            capabilities=["planning", "coordination", "escalation", "strategy"],
            can_review=True, is_fallback=True, max_concurrent=3,
        ),
    }


@pytest.fixture
def router(profiles):
    return AgentRouter(agent_profiles=profiles)


def make_task(**overrides):
    base = {
        "id": "t1", "title": "Test Task", "status": "pending",
        "assignee": "zhangfei-dev", "current_agent": None,
        "next_capability": None, "task_type": "coding",
        "description": "Write some code",
    }
    base.update(overrides)
    return base


# ---------------------------------------------------------------------------
# T1: 确定性快速路径
# ---------------------------------------------------------------------------

class TestDeterministic:
    def test_local_action(self, router):
        """机械检查 → daemon"""
        d = router.route(make_task(), action_type="L1_guardrail")
        assert d.agent_id == "daemon"
        assert d.mode == "deterministic"

    def test_format_check_local(self, router):
        d = router.route(make_task(), action_type="format_check")
        assert d.agent_id == "daemon"

    def test_retry_same_agent(self, router):
        """retry → 原执行者"""
        d = router.route(make_task(current_agent="zhangfei-dev"),
                         action_type="retry")
        assert d.agent_id == "zhangfei-dev"
        assert d.mode == "deterministic"

    def test_direct_assignee(self, router):
        """有 assignee 且非生命周期流转 → 直接用"""
        d = router.route(make_task(), action_type="execute")
        assert d.agent_id == "zhangfei-dev"
        assert d.mode == "deterministic"


# ---------------------------------------------------------------------------
# T2: Mode B — Agent 声明式交接
# ---------------------------------------------------------------------------

class TestAgentHandoff:
    def test_handoff_review(self, router):
        """张飞说需要 review → 匹配司马懿"""
        d = router.route(make_task(
            current_agent="zhangfei-dev",
            next_capability="review",
        ))
        assert d.agent_id == "simayi-challenger"
        assert d.mode == "agent_handoff"
        assert "review" in d.reason

    def test_handoff_excludes_current(self, router):
        """交接排除当前执行者（不能交给自己）"""
        d = router.route(make_task(
            current_agent="simayi-challenger",
            next_capability="review",
        ))
        # simayi 被排除，只剩 pangtong（can_review=True 且有 review 能力）
        assert d.agent_id != "simayi-challenger"

    def test_handoff_invalid_capability_ignored(self, router):
        """BUG-2: 非法 next_capability 被忽略，不进路由"""
        d = router.route(make_task(
            current_agent="zhangfei-dev",
            next_capability="nonexistent_capability",
        ), action_type="execute")
        # 不应该走 handoff，应该走 assignee 直派或其他
        assert d.mode != "agent_handoff"

    def test_handoff_known_but_no_match(self):
        """合法 capability 但无匹配 Agent → 降级"""
        router2 = AgentRouter(agent_profiles={
            "zhangfei-dev": AgentProfile(
                agent_id="zhangfei-dev",
                capabilities=["coding"],
            ),
        })
        d = router2.route(make_task(
            assignee=None,  # 无 assignee，不会走快速路径 4
            current_agent="zhangfei-dev",
            next_capability="review",  # 合法但在 profiles 中无人匹配
        ))
        # 无 LLM driver、无 assignee → fallback 庞统
        assert d.agent_id == "pangtong-fujunshi"
        assert d.mode == "fallback"


# ---------------------------------------------------------------------------
# T3: 生命周期流转
# ---------------------------------------------------------------------------

class TestLifecycle:
    def test_review_action(self, router):
        """review action_type → 查 review 能力，排除 current_agent"""
        d = router.route(make_task(
            current_agent="zhangfei-dev",
        ), action_type="review")
        assert d.agent_id == "simayi-challenger"
        assert d.mode == "deterministic"

    def test_review_excludes_executor(self, router):
        """review 排除执行者（张飞不能审张飞）"""
        d = router.route(make_task(
            current_agent="zhangfei-dev",
            assignee="zhangfei-dev",
        ), action_type="review")
        assert d.agent_id != "zhangfei-dev"
        assert d.agent_id == "simayi-challenger"


# ---------------------------------------------------------------------------
# T4: Mode A — LLM 路由
# ---------------------------------------------------------------------------

class TestLLMRoute:
    def test_llm_returns_valid(self, router):
        """LLM 返回合法 Agent"""
        mock_driver = MagicMock(spec=LLMDriver)
        mock_driver.route.return_value = RouteDecision(
            agent_id="simayi-challenger",
            reason="LLM selected reviewer",
            mode="llm_route",
            confidence=0.9,
            model="zhipu/glm-5.1",
            latency_ms=1200,
        )
        router.llm_driver = mock_driver

        # 无 assignee、无 next_capability、非 lifecycle → 走 LLM
        d = router.route(make_task(
            assignee=None, current_agent=None,
            next_capability=None,
        ))
        assert d.agent_id == "simayi-challenger"
        assert d.mode == "llm_route"
        assert d.confidence == 0.9

    def test_llm_invalid_agent_fallback(self, router):
        """LLM 返回不存在的 Agent → fallback"""
        mock_driver = MagicMock(spec=LLMDriver)
        mock_driver.route.return_value = RouteDecision(
            agent_id="nonexistent-agent",
            reason="Bad pick",
            mode="llm_route",
            confidence=0.8,
            latency_ms=500,
        )
        router.llm_driver = mock_driver

        d = router.route(make_task(assignee=None))
        assert d.agent_id == "pangtong-fujunshi"
        assert d.mode == "fallback"

    def test_llm_low_confidence_fallback(self, router):
        """LLM 置信度低于阈值 → fallback"""
        mock_driver = MagicMock(spec=LLMDriver)
        mock_driver.route.return_value = RouteDecision(
            agent_id="simayi-challenger",
            reason="Not sure",
            mode="llm_route",
            confidence=0.5,
            latency_ms=300,
        )
        router.llm_driver = mock_driver

        d = router.route(make_task(assignee=None))
        assert d.agent_id == "pangtong-fujunshi"
        assert d.mode == "fallback"
        assert d.confidence == 0.5


# ---------------------------------------------------------------------------
# T5: Fallback
# ---------------------------------------------------------------------------

class TestFallback:
    def test_no_llm_no_match(self, router):
        """无 LLM、无匹配 → fallback 庞统"""
        d = router.route(make_task(
            assignee=None, current_agent=None,
            next_capability=None, task_type="unknown_type",
        ), action_type="")
        assert d.agent_id == "pangtong-fujunshi"
        assert d.mode == "fallback"


# ---------------------------------------------------------------------------
# T6: Capability 校验
# ---------------------------------------------------------------------------

class TestCapabilityValidation:
    def test_known_capabilities_populated(self, router):
        """profiles 自动填充 known capabilities"""
        assert "coding" in router._known_capabilities
        assert "review" in router._known_capabilities
        assert "escalation" in router._known_capabilities

    def test_validate_known(self, router):
        assert router._validate_capability("coding") is True
        assert router._validate_capability("review") is True

    def test_validate_unknown(self, router):
        assert router._validate_capability("administration") is False
        assert router._validate_capability("hack") is False

    def test_known_capabilities_set(self):
        """KNOWN_CAPABILITIES 包含所有预期能力"""
        assert "coding" in KNOWN_CAPABILITIES
        assert "review" in KNOWN_CAPABILITIES
        assert "escalation" in KNOWN_CAPABILITIES


# ---------------------------------------------------------------------------
# T7: Latency 记录
# ---------------------------------------------------------------------------

class TestLatency:
    def test_deterministic_has_latency(self, router):
        d = router.route(make_task(), action_type="L1_guardrail")
        assert d.latency_ms >= 0

    def test_handoff_has_latency(self, router):
        d = router.route(make_task(
            current_agent="zhangfei-dev",
            next_capability="review",
        ))
        assert d.latency_ms >= 0
