"""
Tests for pure logic in SourceBasedSearchStrategy:
- Long query detection (lines 250-264)
- Question assembly deduplication (lines 281-286)
- Progress percentage formula (lines 227-229)
- Question context limiting (lines 317-324)

Does NOT duplicate _format_search_results_as_context tests (already in test_strategy_pure_logic.py).
"""

from unittest.mock import Mock


def _make_strategy(**overrides):
    """Build a minimal mock object with the attributes used by the logic under test."""
    s = Mock()
    s.search_original_query = overrides.get("search_original_query", True)
    s.questions_by_iteration = {}
    s.all_links_of_system = []
    s.progress_callback = overrides.get("progress_callback", None)
    s._settings_snapshot = overrides.get("settings_snapshot", {})
    s.question_generator = Mock()
    s.get_setting = overrides.get(
        "get_setting", lambda key, default=None: default
    )
    return s


# ---------------------------------------------------------------------------
# 2a. Long query detection
# ---------------------------------------------------------------------------


class TestLongQueryDetection:
    """Tests for long query detection (lines 250-264)."""

    def test_query_at_max_length_stays_true(self):
        """Query exactly at 300 chars → search_original_query stays True."""
        s = _make_strategy()
        query = "a" * 300
        max_len = 300
        # Simulate the logic
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        assert s.search_original_query is True

    def test_query_over_max_becomes_false(self):
        """Query at 301 chars → search_original_query becomes False."""
        s = _make_strategy()
        query = "a" * 301
        max_len = 300
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        assert s.search_original_query is False

    def test_whitespace_stripped_before_check(self):
        """Leading/trailing whitespace stripped before length check."""
        s = _make_strategy()
        query = "  " + "a" * 299 + "  "  # 299 actual chars + whitespace
        max_len = 300
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        assert s.search_original_query is True  # 299 < 300

    def test_custom_max_from_settings(self):
        """Custom max_query_length (500) from settings."""
        s = _make_strategy()
        query = "a" * 450
        max_len = 500
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        assert s.search_original_query is True  # 450 < 500

    def test_search_original_query_false_skips_check(self):
        """search_original_query=False initially → length check skipped."""
        s = _make_strategy(search_original_query=False)
        query = "a" * 1000
        max_len = 300
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        # Still False — the check never ran because initial value was False
        assert s.search_original_query is False

    def test_empty_query_under_limit(self):
        """Empty query has 0 chars, under any limit."""
        s = _make_strategy()
        query = ""
        max_len = 300
        if s.search_original_query and len(query.strip()) > max_len:
            s.search_original_query = False
        assert s.search_original_query is True


# ---------------------------------------------------------------------------
# 2b. Question assembly deduplication
# ---------------------------------------------------------------------------


class TestQuestionAssembly:
    """Tests for question assembly deduplication (lines 281-286)."""

    def test_query_prepended_when_not_in_questions(self):
        """search_original_query=True, query NOT in questions → prepended."""
        query = "my query"
        questions = ["q1", "q2"]
        search_original_query = True
        all_questions = (
            [query] + questions
            if search_original_query and query not in questions
            else questions
        )
        assert all_questions == ["my query", "q1", "q2"]

    def test_query_not_duplicated_when_already_present(self):
        """search_original_query=True, query in questions → NOT duplicated."""
        query = "my query"
        questions = ["my query", "q2"]
        search_original_query = True
        all_questions = (
            [query] + questions
            if search_original_query and query not in questions
            else questions
        )
        assert all_questions == ["my query", "q2"]

    def test_query_skipped_when_flag_false(self):
        """search_original_query=False → questions returned without query."""
        query = "my query"
        questions = ["q1", "q2"]
        search_original_query = False
        all_questions = (
            [query] + questions
            if search_original_query and query not in questions
            else questions
        )
        assert all_questions == ["q1", "q2"]

    def test_empty_questions_with_flag_true(self):
        """Empty questions + flag=True → returns [query]."""
        query = "my query"
        questions = []
        search_original_query = True
        all_questions = (
            [query] + questions
            if search_original_query and query not in questions
            else questions
        )
        assert all_questions == ["my query"]

    def test_order_preserved_query_first(self):
        """Original query always first when included."""
        query = "original"
        questions = ["q1", "q2", "q3"]
        search_original_query = True
        all_questions = (
            [query] + questions
            if search_original_query and query not in questions
            else questions
        )
        assert all_questions[0] == "original"
        assert all_questions[1:] == ["q1", "q2", "q3"]


# ---------------------------------------------------------------------------
# 2c. Progress percentage formula
# ---------------------------------------------------------------------------


class TestProgressFormula:
    """Tests for iteration_progress_base formula (line 227-229)."""

    def _calc(self, iteration, iterations_to_run):
        return 5 + (iteration - 1) * (70 / iterations_to_run)

    def test_iter1_of_2(self):
        assert self._calc(1, 2) == 5.0

    def test_iter2_of_2(self):
        assert self._calc(2, 2) == 40.0

    def test_iter1_of_5(self):
        assert self._calc(1, 5) == 5.0

    def test_iter5_of_5(self):
        # 5 + 4*(70/5) = 5 + 56 = 61
        assert self._calc(5, 5) == 61.0

    def test_single_iteration_no_division_by_zero(self):
        """iterations_to_run=1 → safe, returns 5."""
        assert self._calc(1, 1) == 5.0


# ---------------------------------------------------------------------------
# 2d. Question context limiting
# ---------------------------------------------------------------------------


class TestQuestionContextLimiting:
    """Tests for question context limiting (lines 317-324)."""

    def test_50_results_limit_30_returns_last_30(self):
        results = list(range(50))
        limit = 30
        sliced = results[-limit:]
        assert len(sliced) == 30
        assert sliced[0] == 20  # last 30 of 0..49

    def test_10_results_limit_30_returns_all(self):
        results = list(range(10))
        limit = 30
        sliced = results[-limit:]
        assert len(sliced) == 10

    def test_default_limit_is_30(self):
        """get_setting returns 30 by default."""
        s = _make_strategy()
        val = int(s.get_setting("search.question_context_limit", 30))
        assert val == 30

    def test_custom_limit_from_settings(self):
        """Custom limit from settings snapshot."""
        s = _make_strategy(
            get_setting=lambda key, default=None: (
                50 if key == "search.question_context_limit" else default
            )
        )
        val = int(s.get_setting("search.question_context_limit", 30))
        assert val == 50
