"""High-value tests for DiversityExplorer pure logic methods."""

import pytest
from collections import defaultdict
from unittest.mock import patch

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 make_candidate(name, category=None, relevance_score=0.0):
    c = Candidate(name=name)
    c.metadata = {}
    if category:
        c.metadata["diversity_category"] = category
    c.relevance_score = relevance_score
    return c


@pytest.fixture
def explorer():
    """Create a DiversityExplorer with mocked parent init."""
    with patch.object(
        DiversityExplorer, "__init__", lambda self, *a, **kw: None
    ):
        e = DiversityExplorer.__new__(DiversityExplorer)
        e.diversity_threshold = 0.7
        e.category_limit = 10
        e.similarity_threshold = 0.8
        e.category_counts = defaultdict(int)
        e.diversity_categories = set()
        e.max_candidates = 20
        return e


# --- _determine_category ---


class TestDetermineCategory:
    def test_mountain(self, explorer):
        c = Candidate(name="Eagle Peak Mountain")
        assert explorer._determine_category(c) == "mountain"

    def test_summit(self, explorer):
        c = Candidate(name="Grand Summit")
        assert explorer._determine_category(c) == "mountain"

    def test_water_lake(self, explorer):
        c = Candidate(name="Silver Lake")
        assert explorer._determine_category(c) == "water"

    def test_water_river(self, explorer):
        c = Candidate(name="Colorado River")
        assert explorer._determine_category(c) == "water"

    def test_park(self, explorer):
        c = Candidate(name="Yellowstone Park")
        assert explorer._determine_category(c) == "park"

    def test_forest(self, explorer):
        c = Candidate(name="Black Forest Reserve")
        assert explorer._determine_category(c) == "park"

    def test_trail(self, explorer):
        c = Candidate(name="Bright Angel Trail")
        assert explorer._determine_category(c) == "trail"

    def test_canyon(self, explorer):
        c = Candidate(name="Grand Canyon")
        assert explorer._determine_category(c) == "canyon"

    def test_gorge(self, explorer):
        c = Candidate(name="Royal Gorge")
        assert explorer._determine_category(c) == "canyon"

    def test_viewpoint(self, explorer):
        c = Candidate(name="Mather Point Overlook")
        assert explorer._determine_category(c) == "viewpoint"

    def test_cliff(self, explorer):
        c = Candidate(name="Red Cliff Bluff")
        assert explorer._determine_category(c) == "viewpoint"

    def test_coastal_beach(self, explorer):
        c = Candidate(name="Sandy Beach")
        assert explorer._determine_category(c) == "coastal"

    def test_coastal_island(self, explorer):
        c = Candidate(name="Alcatraz Island")
        assert explorer._determine_category(c) == "coastal"

    def test_place_city(self, explorer):
        c = Candidate(name="Denver City")
        assert explorer._determine_category(c) == "place"

    def test_place_town(self, explorer):
        c = Candidate(name="Small Town")
        assert explorer._determine_category(c) == "place"

    def test_other_default(self, explorer):
        c = Candidate(name="Something Else Entirely")
        assert explorer._determine_category(c) == "other"


# --- _calculate_diversity_score ---


class TestCalculateDiversityScore:
    def test_empty_candidates(self, explorer):
        assert explorer._calculate_diversity_score([]) == 0.0

    def test_calls_bit_length_on_category_counts(self, explorer):
        """The implementation uses bit_length() which fails on float p values.
        This test documents that the method raises AttributeError for non-empty input."""
        candidates = [
            make_candidate("A", "mountain"),
            make_candidate("B", "mountain"),
        ]
        with pytest.raises(AttributeError, match="bit_length"):
            explorer._calculate_diversity_score(candidates)


# --- _find_underrepresented_categories ---


class TestFindUnderrepresentedCategories:
    def test_empty_counts(self, explorer):
        explorer.category_counts = defaultdict(int)
        assert explorer._find_underrepresented_categories() == []

    def test_uniform_distribution(self, explorer):
        explorer.category_counts = defaultdict(
            int, {"mountain": 5, "water": 5, "park": 5}
        )
        result = explorer._find_underrepresented_categories()
        assert result == []

    def test_underrepresented_found(self, explorer):
        explorer.category_counts = defaultdict(
            int, {"mountain": 10, "water": 10, "park": 1}
        )
        result = explorer._find_underrepresented_categories()
        assert "park" in result

    def test_category_limit_respected(self, explorer):
        explorer.category_limit = 2
        explorer.category_counts = defaultdict(
            int, {"mountain": 10, "water": 2}
        )
        # water avg = 6, threshold = 3, water=2 < 3 and water=2 >= category_limit=2
        result = explorer._find_underrepresented_categories()
        assert "water" not in result


# --- _is_sufficiently_different ---


class TestIsSufficientlyDifferent:
    def test_identical_name_not_different(self, explorer):
        c = Candidate(name="Grand Canyon Trail")
        existing = [Candidate(name="Grand Canyon Trail")]
        assert explorer._is_sufficiently_different(c, existing) is False

    def test_completely_different_name(self, explorer):
        c = Candidate(name="Silver Lake")
        existing = [Candidate(name="Grand Canyon Trail")]
        assert explorer._is_sufficiently_different(c, existing) is True

    def test_empty_existing(self, explorer):
        c = Candidate(name="Eagle Peak")
        assert explorer._is_sufficiently_different(c, []) is True

    def test_only_checks_last_10(self, explorer):
        c = Candidate(name="Grand Canyon Trail")
        # The identical one is beyond the last 10
        existing = [Candidate(name=f"Unique Name {i}") for i in range(15)]
        existing[0] = Candidate(name="Grand Canyon Trail")
        assert explorer._is_sufficiently_different(c, existing) is True

    def test_high_jaccard_similarity_rejected(self, explorer):
        explorer.similarity_threshold = 0.5
        c = Candidate(name="Grand Canyon East Trail")
        existing = [Candidate(name="Grand Canyon West Trail")]
        # Jaccard: intersection={grand, canyon, trail}=3, union={grand,canyon,east,west,trail}=5
        # 3/5 = 0.6 > 0.5
        assert explorer._is_sufficiently_different(c, existing) is False


# --- _final_diversity_selection ---


class TestFinalDiversitySelection:
    def test_empty_returns_empty(self, explorer):
        assert explorer._final_diversity_selection([]) == []

    def test_balanced_selection(self, explorer):
        explorer.max_candidates = 4
        candidates = [
            make_candidate("A", "mountain", 0.9),
            make_candidate("B", "mountain", 0.8),
            make_candidate("C", "water", 0.7),
            make_candidate("D", "water", 0.6),
            make_candidate("E", "park", 0.5),
            make_candidate("F", "park", 0.4),
        ]
        selected = explorer._final_diversity_selection(candidates)
        categories = [c.metadata.get("diversity_category") for c in selected]
        # Should have representation from each category
        assert len(set(categories)) >= 2

    def test_sorts_by_relevance_within_category(self, explorer):
        explorer.max_candidates = 10
        candidates = [
            make_candidate("Low", "mountain", 0.1),
            make_candidate("High", "mountain", 0.9),
        ]
        selected = explorer._final_diversity_selection(candidates)
        if len(selected) >= 2:
            assert selected[0].name == "High"


# --- _generate_diversity_queries ---


class TestGenerateDiversityQueries:
    def test_generates_for_missing_categories(self, explorer):
        found = [make_candidate("X", "mountain")]
        queries = explorer._generate_diversity_queries("hiking", found)
        # Should generate queries for categories other than mountain
        assert len(queries) > 0
        assert any("lake" in q or "river" in q for q in queries)

    def test_limit_to_three(self, explorer):
        queries = explorer._generate_diversity_queries("hiking", [])
        assert len(queries) <= 3

    def test_entity_type_used_as_base(self, explorer):
        queries = explorer._generate_diversity_queries(
            "hiking", [], entity_type="trail"
        )
        for q in queries:
            assert "trail" in q


# --- _generate_category_queries ---


class TestGenerateCategoryQueries:
    def test_generates_two_per_category(self, explorer):
        queries = explorer._generate_category_queries(
            ["mountain"], "hiking", None
        )
        assert len(queries) == 2

    def test_uses_entity_type(self, explorer):
        queries = explorer._generate_category_queries(
            ["water"], "hiking", "scenic spot"
        )
        assert any("scenic spot" in q for q in queries)

    def test_limit_to_three_categories(self, explorer):
        queries = explorer._generate_category_queries(
            ["mountain", "water", "park", "trail"], "hiking", None
        )
        assert len(queries) == 6  # 3 categories * 2 queries
