"""
Pure-logic tests for DiversityExplorer._rank_by_diversity.

Tests diversity boost calculation, sorting by final_score, and edge cases
— no LLM or search calls.
"""

from unittest.mock import Mock

from local_deep_research.advanced_search_system.candidates.base_candidate import (
    Candidate,
)
from local_deep_research.advanced_search_system.candidate_exploration.diversity_explorer import (
    DiversityExplorer,
)


def _candidate(name, category="other", relevance_score=0.5):
    """Create a Candidate with diversity metadata and relevance_score."""
    c = Candidate(name=name, metadata={"diversity_category": category})
    c.relevance_score = relevance_score
    return c


def _make_explorer(category_counts, candidates_for_relevance=None):
    """Build a minimal Mock with attributes used by _rank_by_diversity.

    Sets _rank_candidates_by_relevance as a callable on the mock so that
    the unbound method call self._rank_candidates_by_relevance(...) works.
    """
    explorer = Mock(spec=[])
    explorer.category_counts = category_counts
    # The method calls self._rank_candidates_by_relevance first.
    # We mock it to return the candidates as-is (preserving relevance_score).
    explorer._rank_candidates_by_relevance = Mock(
        side_effect=lambda candidates, query: candidates
    )
    return explorer


class TestRankByDiversity:
    """Verify diversity boost calculation, sorting, and edge cases."""

    def test_empty_candidates(self):
        """Empty input returns empty list."""
        explorer = _make_explorer({"a": 1})
        result = DiversityExplorer._rank_by_diversity(explorer, [], "query")
        assert result == []

    def test_single_candidate(self):
        """Single candidate gets a final_score assigned."""
        c = _candidate("A", "cat1", 0.8)
        explorer = _make_explorer({"cat1": 1})
        result = DiversityExplorer._rank_by_diversity(explorer, [c], "query")
        assert len(result) == 1
        assert hasattr(result[0], "final_score")

    def test_all_same_category_no_boost(self):
        """All candidates in same category -> diversity_boost = 0 for all."""
        candidates = [_candidate(f"C{i}", "same", 0.5) for i in range(3)]
        explorer = _make_explorer({"same": 3})
        result = DiversityExplorer._rank_by_diversity(
            explorer, candidates, "query"
        )
        # avg_count=3, category_count=3 -> boost = max(0, (3-3)/3) = 0
        for c in result:
            assert c.final_score == 0.5  # relevance_score + 0

    def test_underrepresented_category_gets_boost(self):
        """Minority category gets a positive diversity boost."""
        c_common = _candidate("Common", "popular", 0.5)
        c_rare = _candidate("Rare", "niche", 0.5)
        # popular=3, niche=1 -> avg=2
        # popular boost: max(0, (2-3)/2) = 0
        # niche boost: max(0, (2-1)/2) = 0.5 -> diversity_boost * 0.2 = 0.1
        explorer = _make_explorer({"popular": 3, "niche": 1})
        result = DiversityExplorer._rank_by_diversity(
            explorer, [c_common, c_rare], "query"
        )
        rare_c = next(c for c in result if c.name == "Rare")
        common_c = next(c for c in result if c.name == "Common")
        assert rare_c.final_score > common_c.final_score

    def test_no_relevance_score_defaults_to_zero(self):
        """Candidate without relevance_score attribute defaults to 0.0."""
        c = Candidate(name="NoScore", metadata={"diversity_category": "cat1"})
        # Ensure no relevance_score attribute
        assert not hasattr(c, "relevance_score")
        explorer = _make_explorer({"cat1": 1})
        result = DiversityExplorer._rank_by_diversity(explorer, [c], "query")
        # With no relevance_score, getattr defaults to 0.0
        # avg=1, count=1 -> boost=0, so final_score = 0.0
        assert result[0].final_score == 0.0

    def test_max_boost_capped_at_02(self):
        """Maximum diversity boost is 0.2 (when category is heavily underrepresented)."""
        # avg=10, count=1 -> boost = (10-1)/10 = 0.9 -> 0.9*0.2 = 0.18
        c = _candidate("Unique", "rare", 0.0)
        explorer = _make_explorer({"common": 19, "rare": 1})
        result = DiversityExplorer._rank_by_diversity(explorer, [c], "query")
        assert result[0].final_score <= 0.2

    def test_sort_order_by_final_score(self):
        """Result is sorted by final_score descending."""
        c1 = _candidate("Low", "popular", 0.3)
        c2 = _candidate("High", "niche", 0.8)
        explorer = _make_explorer({"popular": 5, "niche": 1})
        result = DiversityExplorer._rank_by_diversity(
            explorer, [c1, c2], "query"
        )
        assert result[0].final_score >= result[1].final_score

    def test_overrepresented_category_no_negative_boost(self):
        """Overrepresented category gets boost clamped to 0 (not negative)."""
        c = _candidate("Over", "dominant", 0.6)
        # dominant=10, avg=5 -> boost = max(0, (5-10)/5) = max(0, -1) = 0
        explorer = _make_explorer({"dominant": 10})
        result = DiversityExplorer._rank_by_diversity(explorer, [c], "query")
        assert result[0].final_score == 0.6  # No boost applied
