"""Unit tests for openjiuwen_deepsearch.utils.question_model_router (parse_bit + route_question_search_path)."""

import pytest
from unittest.mock import AsyncMock, patch

from openjiuwen_deepsearch.utils import question_model_router as qmr


@pytest.mark.parametrize(
    "text, expected",
    [
        ("", None),
        ("   ", None),
        ("0", 0),
        ("1", 1),
        ("\n0\n", 0),
        ("The answer: 1", 1),
        ("prefix 0 suffix", 0),
        ("no binary here", None),
        ("2", None),
    ],
)
def test_parse_bit(text, expected):
    assert qmr.parse_bit(text) == expected


def test_system_prompt_file_loaded():
    assert isinstance(qmr.QUESTION_ROUTER_SYSTEM_PROMPT, str)
    assert len(qmr.QUESTION_ROUTER_SYSTEM_PROMPT) > 50
    assert "0 or 1" in qmr.QUESTION_ROUTER_SYSTEM_PROMPT


@pytest.mark.asyncio
async def test_route_empty_question_no_llm_defaults_deepsearch():
    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        new_callable=AsyncMock,
    ) as mock_llm:
        out = await qmr.route_question_search_path("  \n", {"model": object(), "model_name": "m"})
    assert out == 1
    mock_llm.assert_not_called()


@pytest.mark.asyncio
async def test_route_respects_model_output_zero():
    async def fake_ainvoke(*_a, **_k):
        return {"content": "0"}

    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        side_effect=fake_ainvoke,
    ) as mock_llm:
        out = await qmr.route_question_search_path("Who won X in 2020?", {"model": object(), "model_name": "m"})
    assert out == 0
    assert mock_llm.call_count == 1


@pytest.mark.asyncio
async def test_route_respects_model_output_one():
    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        new=AsyncMock(return_value={"content": "1"}),
    ):
        out = await qmr.route_question_search_path("long clue chain A then B then C", {"model": object(), "model_name": "m"})
    assert out == 1


@pytest.mark.asyncio
async def test_route_unparseable_model_output_defaults_deepsearch():
    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        new=AsyncMock(return_value={"content": "maybe"}),
    ):
        out = await qmr.route_question_search_path("q", {"model": object(), "model_name": "m"})
    assert out == 1


@pytest.mark.asyncio
async def test_route_llm_exception_defaults_deepsearch():
    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        new=AsyncMock(side_effect=RuntimeError("api down")),
    ):
        out = await qmr.route_question_search_path("q", {"model": object(), "model_name": "m"})
    assert out == 1


@pytest.mark.asyncio
async def test_route_passes_extra_body_to_llm():
    async def capture(*_a, **kwargs):
        assert kwargs.get("extra_body") == {"k": 1}
        return {"content": "1"}

    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        side_effect=capture,
    ):
        await qmr.route_question_search_path("q", {"model": object(), "model_name": "m"}, extra_body={"k": 1})


@pytest.mark.asyncio
async def test_route_messages_include_system_and_user():
    seen = {}

    async def capture(_llm, messages, **kwargs):
        seen["messages"] = messages
        return {"content": "0"}

    with patch(
        "openjiuwen_deepsearch.utils.common_utils.llm_utils.ainvoke_llm_with_stats",
        side_effect=capture,
    ):
        await qmr.route_question_search_path("user question here", {"model": object(), "model_name": "m"})

    assert len(seen["messages"]) == 2
    assert seen["messages"][0]["role"] == "system"
    assert qmr.QUESTION_ROUTER_SYSTEM_PROMPT in seen["messages"][0]["content"]
    assert seen["messages"][1] == {"role": "user", "content": "user question here"}