"""Tests for autodebug.agent.loop_detector — catches pagination black holes."""

from autodebug.agent.loop_detector import LoopDetector, LOOP_THRESHOLD


class TestLoopDetector:
    """Core loop detection behavior."""

    def test_no_false_positive_for_distinct_calls(self):
        d = LoopDetector()
        assert d.record_call("scan_codebase", {"page": 1}) is None
        assert d.record_call("read_file", {"path": "a.py"}) is None
        assert d.record_call("run_tests", {"path": "tests/"}) is None
        assert d.loops_detected == 0

    def test_exact_duplicate_detected_at_threshold(self):
        d = LoopDetector()
        for _ in range(LOOP_THRESHOLD - 1):
            assert d.record_call("scan_codebase", {"page": 1}) is None
        msg = d.record_call("scan_codebase", {"page": 1})
        assert msg is not None
        assert "LOOP DETECTED" in msg
        assert "scan_codebase" in msg
        assert d.loops_detected == 1

    def test_no_false_positive_below_threshold(self):
        d = LoopDetector()
        for _ in range(LOOP_THRESHOLD - 1):
            assert d.record_call("scan_codebase", {"page": 1}) is None
        assert d.loops_detected == 0

    def test_pagination_pattern_detected(self):
        d = LoopDetector()
        d.record_call("scan_codebase", {"page": 1})
        d.record_call("scan_codebase", {"page": 2})
        msg = d.record_call("scan_codebase", {"page": 3})
        assert msg is not None
        assert "PAGINATION" in msg
        assert d.loops_detected == 1

    def test_pagination_with_offset_param(self):
        d = LoopDetector()
        d.record_call("list_items", {"offset": 1})
        d.record_call("list_items", {"offset": 2})
        msg = d.record_call("list_items", {"offset": 3})
        assert msg is not None
        assert "PAGINATION" in msg

    def test_no_double_warning(self):
        d = LoopDetector()
        for _ in range(LOOP_THRESHOLD):
            d.record_call("scan_codebase", {"page": 1})
        # Already warned — should not warn again
        msg = d.record_call("scan_codebase", {"page": 1})
        assert msg is None
        assert d.loops_detected == 1

    def test_pagination_no_double_warning(self):
        d = LoopDetector()
        d.record_call("scan_codebase", {"page": 1})
        d.record_call("scan_codebase", {"page": 2})
        d.record_call("scan_codebase", {"page": 3})  # triggers warning
        msg = d.record_call("scan_codebase", {"page": 4})
        assert msg is None  # no second warning

    def test_different_args_are_different_calls(self):
        d = LoopDetector()
        d.record_call("read_file", {"path": "a.py"})
        d.record_call("read_file", {"path": "b.py"})
        d.record_call("read_file", {"path": "c.py"})
        assert d.loops_detected == 0

    def test_reset_clears_state(self):
        d = LoopDetector()
        for _ in range(LOOP_THRESHOLD):
            d.record_call("scan_codebase", {"page": 1})
        assert d.loops_detected == 1
        d.reset()
        assert d.loops_detected == 0
        assert len(d.recent_calls) == 0
        assert len(d._warned_patterns) == 0
        assert len(d._call_counts) == 0

    def test_empty_args_treated_as_empty_dict(self):
        d = LoopDetector()
        for _ in range(LOOP_THRESHOLD - 1):
            assert d.record_call("some_tool", None) is None
        msg = d.record_call("some_tool", None)
        assert msg is not None
        assert "LOOP DETECTED" in msg

    def test_multiple_different_loops_detected(self):
        d = LoopDetector()
        # Loop 1: scan_codebase
        for _ in range(LOOP_THRESHOLD):
            d.record_call("scan_codebase", {"page": 1})
        # Loop 2: list_errors
        for _ in range(LOOP_THRESHOLD):
            d.record_call("list_errors", {"status": "open"})
        assert d.loops_detected == 2

    def test_low_page_numbers_no_pagination_warning(self):
        """page=1 and page=2 shouldn't trigger pagination (below threshold)."""
        d = LoopDetector()
        d.record_call("scan_codebase", {"page": 1})
        msg = d.record_call("scan_codebase", {"page": 2})
        assert msg is None or "PAGINATION" not in (msg or "")
