"""Unit tests for IntentRouter multi-candidate keyword scoring."""

from __future__ import annotations

from agentkit.router.intent import IntentRouter
from agentkit.skills.base import Skill, SkillConfig


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


def _make_skill(name: str, keywords: list[str], description: str = "") -> Skill:
    """Create a Skill with the given name and intent keywords."""
    config = SkillConfig(
        name=name,
        agent_type="skill",
        description=description or f"Skill {name}",
        task_mode="llm_generate",
        prompt={"identity": f"You are {name}"},
        intent={"keywords": keywords, "description": description or f"Skill {name}"},
    )
    return Skill(config=config)


def _make_skills(*specs: tuple[str, list[str]]) -> list[Skill]:
    """Create multiple skills from (name, keywords) tuples."""
    return [_make_skill(name, kws) for name, kws in specs]


# ---------------------------------------------------------------------------
# Tests: Single-candidate match (backward compatible)
# ---------------------------------------------------------------------------


class TestSingleCandidateMatch:
    """When only one skill matches, behavior is identical to old first-match."""

    def test_single_skill_matches(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["规划", "执行"]), ("skill_b", ["搜索", "查询"]))
        result = router._match_keywords({"content": "帮我规划一个项目"}, skills)
        assert result is not None
        assert result.matched_skill == "skill_a"
        assert result.method == "keyword"

    def test_single_keyword_match_confidence(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["规划"]))
        result = router._match_keywords({"content": "规划任务"}, skills)
        assert result is not None
        # 1 keyword matched → confidence = min(1.0, 0.5 + 0.1 * 1) = 0.6
        assert result.confidence == 0.6


# ---------------------------------------------------------------------------
# Tests: Multi-candidate scoring
# ---------------------------------------------------------------------------


class TestMultiCandidateScoring:
    """When multiple skills match, the best-scored one wins."""

    def test_longer_keyword_wins(self) -> None:
        """'调研报告' (4 chars) beats '报告' (2 chars) on same input."""
        router = IntentRouter()
        skills = _make_skills(
            ("plan_exec", ["规划", "报告"]),
            ("goal_driven", ["调研报告", "生成"]),
        )
        result = router._match_keywords({"content": "规划一个调研报告"}, skills)
        assert result is not None
        # plan_exec: "规划"(2) + "报告"(2) = 4; goal_driven: "调研报告"(4) = 4
        # Same score → alphabetical: goal_driven < plan_exec
        assert result.matched_skill == "goal_driven"

    def test_more_keywords_wins(self) -> None:
        """Skill matching 3 keywords beats skill matching 1 keyword."""
        router = IntentRouter()
        skills = _make_skills(
            ("skill_a", ["分析"]),
            ("skill_b", ["分析", "市场", "趋势"]),
        )
        result = router._match_keywords({"content": "分析市场趋势"}, skills)
        assert result is not None
        # skill_a: "分析"(2) = 2; skill_b: "分析"(2)+"市场"(2)+"趋势"(2) = 6
        assert result.matched_skill == "skill_b"

    def test_same_score_alphabetical(self) -> None:
        """When scores are equal, alphabetical name order breaks the tie."""
        router = IntentRouter()
        skills = _make_skills(
            ("zebra_skill", ["分析"]),
            ("alpha_skill", ["分析"]),
        )
        result = router._match_keywords({"content": "分析数据"}, skills)
        assert result is not None
        assert result.matched_skill == "alpha_skill"


# ---------------------------------------------------------------------------
# Tests: No match
# ---------------------------------------------------------------------------


class TestNoMatch:
    def test_no_keyword_match(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["搜索"]), ("skill_b", ["查询"]))
        result = router._match_keywords({"content": "你好"}, skills)
        assert result is None

    def test_empty_keywords_list(self) -> None:
        """Skill with empty keywords list does not participate in matching."""
        router = IntentRouter()
        skills = [_make_skill("empty_kw", [])]
        result = router._match_keywords({"content": "anything"}, skills)
        assert result is None


# ---------------------------------------------------------------------------
# Tests: Case insensitivity
# ---------------------------------------------------------------------------


class TestCaseInsensitivity:
    def test_english_keyword_case_insensitive(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["Search"]))
        result = router._match_keywords({"content": "please search for this"}, skills)
        assert result is not None
        assert result.matched_skill == "skill_a"


# ---------------------------------------------------------------------------
# Tests: Substring matching
# ---------------------------------------------------------------------------


class TestSubstringMatch:
    def test_chinese_substring_match(self) -> None:
        """Chinese keyword '报告' should match input containing '报告'."""
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["报告"]))
        result = router._match_keywords({"content": "生成一份报告"}, skills)
        assert result is not None
        assert result.matched_skill == "skill_a"


# ---------------------------------------------------------------------------
# Tests: Confidence calculation
# ---------------------------------------------------------------------------


class TestConfidenceCalculation:
    def test_one_keyword_confidence(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["分析"]))
        result = router._match_keywords({"content": "分析数据"}, skills)
        assert result is not None
        assert result.confidence == 0.6  # 0.5 + 0.1 * 1

    def test_three_keywords_confidence(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["分析", "市场", "趋势"]))
        result = router._match_keywords({"content": "分析市场趋势"}, skills)
        assert result is not None
        assert result.confidence == 0.8  # 0.5 + 0.1 * 3

    def test_confidence_capped_at_one(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["a", "b", "c", "d", "e", "f"]))
        result = router._match_keywords({"content": "a b c d e f"}, skills)
        assert result is not None
        assert result.confidence == 1.0  # min(1.0, 0.5 + 0.1 * 6 = 1.1)


# ---------------------------------------------------------------------------
# Tests: Edge cases
# ---------------------------------------------------------------------------


class TestEdgeCases:
    def test_empty_input_text(self) -> None:
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["分析"]))
        result = router._match_keywords({"content": ""}, skills)
        assert result is None

    def test_nested_input_data(self) -> None:
        """_extract_string_values should handle nested dicts/lists."""
        router = IntentRouter()
        skills = _make_skills(("skill_a", ["分析"]))
        result = router._match_keywords(
            {"message": {"text": "分析数据", "meta": {"role": "user"}}},
            skills,
        )
        assert result is not None
        assert result.matched_skill == "skill_a"
