"""
Tests for advanced_search_system/candidate_exploration/progressive_explorer.py

Tests cover:
- SearchProgress dataclass
- ProgressiveExplorer class
- Parallel search execution
- Candidate extraction
"""

from unittest.mock import Mock


class TestSearchProgressInit:
    """Tests for SearchProgress initialization."""

    def test_init_creates_empty_searched_terms(self):
        """Test that searched_terms is initialized empty."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()

        assert progress.searched_terms == set()

    def test_init_creates_empty_found_candidates(self):
        """Test that found_candidates is initialized empty."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()

        assert progress.found_candidates == {}

    def test_init_creates_empty_verified_facts(self):
        """Test that verified_facts is initialized empty."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()

        assert progress.verified_facts == {}

    def test_init_creates_empty_entity_coverage(self):
        """Test that entity_coverage is initialized empty."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()

        assert progress.entity_coverage == {}

    def test_init_search_depth_is_zero(self):
        """Test that search_depth is initialized to 0."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()

        assert progress.search_depth == 0

    def test_multiple_instances_independent(self):
        """Test that multiple instances don't share mutable defaults."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress1 = SearchProgress()
        progress2 = SearchProgress()

        progress1.searched_terms.add("test")
        progress1.found_candidates["candidate"] = 0.5

        assert "test" not in progress2.searched_terms
        assert "candidate" not in progress2.found_candidates


class TestSearchProgressUpdateCoverage:
    """Tests for SearchProgress.update_coverage method."""

    def test_adds_entity_to_coverage(self):
        """Test that entity is added to coverage."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "John Smith")

        assert "john smith" in progress.entity_coverage["names"]

    def test_creates_entity_type_if_not_exists(self):
        """Test that entity type is created if it doesn't exist."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("temporal", "2024")

        assert "temporal" in progress.entity_coverage
        assert "2024" in progress.entity_coverage["temporal"]

    def test_lowercases_entity(self):
        """Test that entities are lowercased."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "JOHN")

        assert "john" in progress.entity_coverage["names"]

    def test_adds_multiple_entities_to_same_type(self):
        """Test adding multiple entities to same type."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "John")
        progress.update_coverage("names", "Jane")

        assert "john" in progress.entity_coverage["names"]
        assert "jane" in progress.entity_coverage["names"]


class TestSearchProgressGetUncoveredEntities:
    """Tests for SearchProgress.get_uncovered_entities method."""

    def test_returns_all_when_none_covered(self):
        """Test that all entities are returned when none are covered."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        entities = {"names": ["John", "Jane"], "temporal": ["2024", "2025"]}

        uncovered = progress.get_uncovered_entities(entities)

        assert uncovered == entities

    def test_excludes_covered_entities(self):
        """Test that covered entities are excluded."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "John")

        entities = {"names": ["John", "Jane"]}
        uncovered = progress.get_uncovered_entities(entities)

        assert uncovered == {"names": ["Jane"]}

    def test_returns_empty_when_all_covered(self):
        """Test that empty dict is returned when all are covered."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "John")

        entities = {"names": ["John"]}
        uncovered = progress.get_uncovered_entities(entities)

        assert uncovered == {}

    def test_handles_case_insensitivity(self):
        """Test that coverage check is case-insensitive."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            SearchProgress,
        )

        progress = SearchProgress()
        progress.update_coverage("names", "JOHN")

        entities = {"names": ["john", "Jane"]}
        uncovered = progress.get_uncovered_entities(entities)

        assert uncovered == {"names": ["Jane"]}


class TestProgressiveExplorerInit:
    """Tests for ProgressiveExplorer initialization."""

    def test_stores_search_engine(self):
        """Test that search engine is stored."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_model = Mock()

        explorer = ProgressiveExplorer(mock_engine, mock_model)

        assert explorer.search_engine is mock_engine

    def test_stores_model(self):
        """Test that model is stored."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_model = Mock()

        explorer = ProgressiveExplorer(mock_engine, mock_model)

        assert explorer.model is mock_model

    def test_creates_search_progress(self):
        """Test that SearchProgress is created."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
            SearchProgress,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())

        assert isinstance(explorer.progress, SearchProgress)

    def test_sets_max_results_per_search(self):
        """Test that max_results_per_search is set."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())

        assert explorer.max_results_per_search == 20


class TestProgressiveExplorerExplore:
    """Tests for ProgressiveExplorer.explore method."""

    def test_returns_results_and_progress(self):
        """Test that explore returns both results and progress."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
            SearchProgress,
        )

        mock_engine = Mock()
        mock_engine.run.return_value = []

        explorer = ProgressiveExplorer(mock_engine, Mock())
        results, progress = explorer.explore(["test query"])

        assert isinstance(results, list)
        assert isinstance(progress, SearchProgress)

    def test_tracks_searched_terms(self):
        """Test that searched terms are tracked."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.return_value = []

        explorer = ProgressiveExplorer(mock_engine, Mock())
        _, progress = explorer.explore(["Test Query"])

        assert "test query" in progress.searched_terms

    def test_increments_search_depth(self):
        """Test that search depth is incremented."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.return_value = []

        explorer = ProgressiveExplorer(mock_engine, Mock())

        explorer.explore(["query1"])
        assert explorer.progress.search_depth == 1

        explorer.explore(["query2"])
        assert explorer.progress.search_depth == 2

    def test_aggregates_results_from_multiple_queries(self):
        """Test that results from multiple queries are aggregated."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.side_effect = [
            [{"title": "Result 1"}],
            [{"title": "Result 2"}],
        ]

        explorer = ProgressiveExplorer(mock_engine, Mock())
        results, _ = explorer.explore(["query1", "query2"])

        assert len(results) == 2


class TestGenerateVerificationSearches:
    """Tests for ProgressiveExplorer.generate_verification_searches method."""

    def test_returns_empty_when_no_candidates(self):
        """Test that empty list is returned when no candidates."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())

        result = explorer.generate_verification_searches({}, [])

        assert result == []

    def test_generates_searches_for_top_candidates(self):
        """Test that searches are generated for top candidates."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        candidates = {
            "Candidate A": 0.9,
            "Candidate B": 0.5,
            "Candidate C": 0.3,
        }

        mock_constraint = Mock()
        mock_constraint.description = "test constraint"

        searches = explorer.generate_verification_searches(
            candidates, [mock_constraint]
        )

        assert any("Candidate A" in s for s in searches)

    def test_excludes_already_searched_terms(self):
        """Test that already searched terms are excluded."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        explorer.progress.searched_terms.add('"candidate a" test constraint')

        candidates = {"Candidate A": 0.9}
        mock_constraint = Mock()
        mock_constraint.description = "test constraint"

        searches = explorer.generate_verification_searches(
            candidates, [mock_constraint]
        )

        assert '"Candidate A" test constraint' not in searches

    def test_respects_max_searches(self):
        """Test that max_searches is respected."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        candidates = {"Candidate A": 0.9, "Candidate B": 0.8}

        constraints = [Mock() for _ in range(5)]
        for c in constraints:
            c.description = "constraint"

        searches = explorer.generate_verification_searches(
            candidates, constraints, max_searches=3
        )

        assert len(searches) <= 3


class TestExtractCandidatesFromResults:
    """Tests for ProgressiveExplorer._extract_candidates_from_results method."""

    def test_extracts_quoted_terms(self):
        """Test that quoted terms are extracted as candidates."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        results = [{"title": 'Answer is "John Smith"', "snippet": "Some text"}]

        candidates = explorer._extract_candidates_from_results(results, "query")

        assert "John Smith" in candidates

    def test_assigns_base_confidence_to_quoted(self):
        """Test that quoted terms get base confidence of 0.3."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        results = [{"title": 'The answer is "Test Candidate"', "snippet": ""}]

        candidates = explorer._extract_candidates_from_results(results, "query")

        assert candidates.get("Test Candidate", 0) >= 0.3

    def test_ignores_very_short_terms(self):
        """Test that very short terms are ignored."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        results = [{"title": 'It is "AB"', "snippet": ""}]

        candidates = explorer._extract_candidates_from_results(results, "query")

        assert "AB" not in candidates

    def test_ignores_very_long_terms(self):
        """Test that very long terms are ignored."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        long_term = "A" * 60
        results = [{"title": f'Result "{long_term}"', "snippet": ""}]

        candidates = explorer._extract_candidates_from_results(results, "query")

        assert long_term not in candidates

    def test_handles_empty_results(self):
        """Test handling of empty results."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())

        candidates = explorer._extract_candidates_from_results([], "query")

        assert candidates == {}


class TestSuggestNextSearches:
    """Tests for ProgressiveExplorer.suggest_next_searches method."""

    def test_returns_empty_when_all_covered(self):
        """Test that empty list is returned when all entities covered."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        explorer.progress.update_coverage("names", "John")

        suggestions = explorer.suggest_next_searches({"names": ["John"]})

        assert suggestions == []

    def test_suggests_candidate_combinations(self):
        """Test that candidate-entity combinations are suggested."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        explorer.progress.found_candidates["Test Candidate"] = 0.8

        entities = {"temporal": ["2024", "2025"]}
        suggestions = explorer.suggest_next_searches(entities)

        assert any("Test Candidate" in s for s in suggestions)

    def test_respects_max_suggestions(self):
        """Test that max_suggestions is respected."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        explorer.progress.found_candidates["Candidate"] = 0.9

        entities = {
            "temporal": ["2020", "2021", "2022", "2023", "2024"],
            "names": ["A", "B", "C", "D"],
        }

        suggestions = explorer.suggest_next_searches(
            entities, max_suggestions=3
        )

        assert len(suggestions) <= 3

    def test_excludes_already_searched(self):
        """Test that already searched terms are excluded."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        explorer.progress.searched_terms.add('"candidate" 2024')
        explorer.progress.found_candidates["Candidate"] = 0.9

        entities = {"temporal": ["2024"]}
        suggestions = explorer.suggest_next_searches(entities)

        assert '"Candidate" 2024' not in suggestions


class TestUpdateEntityCoverage:
    """Tests for ProgressiveExplorer._update_entity_coverage method."""

    def test_updates_coverage_when_entity_in_query(self):
        """Test that coverage is updated when entity is in query."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        entities = {"names": ["John"]}

        explorer._update_entity_coverage("Search for John", entities)

        assert "john" in explorer.progress.entity_coverage.get("names", set())

    def test_case_insensitive_matching(self):
        """Test that entity matching is case-insensitive."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        explorer = ProgressiveExplorer(Mock(), Mock())
        entities = {"names": ["JOHN"]}

        explorer._update_entity_coverage("search for john", entities)

        assert "john" in explorer.progress.entity_coverage.get("names", set())


class TestParallelSearch:
    """Tests for ProgressiveExplorer._parallel_search method."""

    def test_returns_list_of_tuples(self):
        """Test that parallel search returns list of tuples."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.return_value = [{"title": "Result"}]

        explorer = ProgressiveExplorer(mock_engine, Mock())
        results = explorer._parallel_search(["query1"], max_workers=1)

        assert isinstance(results, list)
        assert isinstance(results[0], tuple)
        assert results[0][0] == "query1"

    def test_handles_search_errors(self):
        """Test that search errors are handled gracefully."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.side_effect = Exception("Search error")

        explorer = ProgressiveExplorer(mock_engine, Mock())
        results = explorer._parallel_search(["query1"], max_workers=1)

        # Should return empty list for the query, not raise
        assert results[0][1] == []

    def test_handles_none_results(self):
        """Test that None results are converted to empty list."""
        from local_deep_research.advanced_search_system.candidate_exploration.progressive_explorer import (
            ProgressiveExplorer,
        )

        mock_engine = Mock()
        mock_engine.run.return_value = None

        explorer = ProgressiveExplorer(mock_engine, Mock())
        results = explorer._parallel_search(["query1"], max_workers=1)

        assert results[0][1] == []
