"""Unit tests for AntiStallMonitor."""

from unittest.mock import AsyncMock, patch

import pytest

from navi.core.anti_stall import AntiStallMonitor
from navi.llm.base import Message, ToolCallRequest
from tests.conftest_factory import make_profile


class TestPreTurn:
    @pytest.mark.asyncio
    async def test_no_message_when_not_stalled(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=8)
        monitor = AntiStallMonitor(profile)
        result = await monitor.pre_turn("s1", iteration=1)
        assert result is None

    @pytest.mark.asyncio
    async def test_anti_stall_no_todo(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=3)
        monitor = AntiStallMonitor(profile)
        monitor.stall_no_todo = 3
        msg = await monitor.pre_turn("s1", iteration=1)
        assert msg is not None
        assert msg.role == "system"
        assert "no todo progress for 3 iterations" in msg.content
        assert "Anti-stall warning" in msg.content

    @pytest.mark.asyncio
    async def test_anti_stall_repeat_tools(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=3)
        monitor = AntiStallMonitor(profile)
        monitor.stall_repeat_tools = 3
        msg = await monitor.pre_turn("s1", iteration=1)
        assert msg is not None
        assert "identical tool calls repeated 3 times" in msg.content

    @pytest.mark.asyncio
    async def test_anti_stall_skipped_on_first_iteration(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=1)
        monitor = AntiStallMonitor(profile)
        monitor.stall_no_todo = 10
        msg = await monitor.pre_turn("s1", iteration=0)
        assert msg is None

    @pytest.mark.asyncio
    async def test_disabled_anti_stall_returns_none(self):
        profile = make_profile(anti_stall_enabled=False)
        monitor = AntiStallMonitor(profile)
        monitor.stall_no_todo = 100
        msg = await monitor.pre_turn("s1", iteration=5)
        assert msg is None


class TestPostTurn:
    @pytest.mark.asyncio
    async def test_todo_progress_resets_stall_counter(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=8)
        monitor = AntiStallMonitor(profile)
        monitor.stall_no_todo = 5
        monitor._todo_snapshot = frozenset({("task1", "pending")})
        with patch(
            "navi.tools.todo.get_task_snapshot",
            new=AsyncMock(return_value=frozenset({("task1", "done")})),
        ):
            await monitor.post_turn("s1", [])
        assert monitor.stall_no_todo == 0

    @pytest.mark.asyncio
    async def test_no_todo_progress_increments_stall_counter(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=8)
        monitor = AntiStallMonitor(profile)
        snapshot = frozenset({("task1", "pending")})
        monitor._todo_snapshot = snapshot
        with patch(
            "navi.tools.todo.get_task_snapshot",
            new=AsyncMock(return_value=snapshot),
        ):
            await monitor.post_turn("s1", [])
        assert monitor.stall_no_todo == 1

    @pytest.mark.asyncio
    async def test_repeated_tool_calls_increment_repeat_counter(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=8)
        monitor = AntiStallMonitor(profile)
        tc = ToolCallRequest(id="1", name="fs", arguments={"path": "/tmp"})
        # first call sets baseline
        with patch(
            "navi.tools.todo.get_task_snapshot",
            new=AsyncMock(return_value=frozenset()),
        ):
            await monitor.post_turn("s1", [tc])
        assert monitor.stall_repeat_tools == 0
        # identical call again
        with patch(
            "navi.tools.todo.get_task_snapshot",
            new=AsyncMock(return_value=frozenset()),
        ):
            await monitor.post_turn("s1", [tc])
        assert monitor.stall_repeat_tools == 1

    @pytest.mark.asyncio
    async def test_different_tool_calls_reset_repeat_counter(self):
        profile = make_profile(anti_stall_enabled=True, anti_stall_threshold=8)
        monitor = AntiStallMonitor(profile)
        tc1 = ToolCallRequest(id="1", name="fs", arguments={"path": "/tmp"})
        tc2 = ToolCallRequest(id="2", name="fs", arguments={"path": "/other"})
        with patch(
            "navi.tools.todo.get_task_snapshot",
            new=AsyncMock(return_value=frozenset()),
        ):
            await monitor.post_turn("s1", [tc1])
            await monitor.post_turn("s1", [tc2])
        assert monitor.stall_repeat_tools == 0

    @pytest.mark.asyncio
    async def test_disabled_anti_stall_does_not_fetch_snapshot(self):
        profile = make_profile(anti_stall_enabled=False)
        monitor = AntiStallMonitor(profile)
        with patch("navi.tools.todo.get_task_snapshot") as mock_snap:
            await monitor.post_turn("s1", [])
        mock_snap.assert_not_called()
