"""
Tests for the ConstraintGuidedExplorer class.

Tests cover:
- Initialization and configuration
- Constraint prioritization
- Constraint-specific query generation
- Cross-constraint exploration
- Early validation of candidates
- Name pattern queries
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict
from unittest.mock import Mock


class MockConstraintType(Enum):
    """Mock constraint type for testing."""

    PROPERTY = "property"
    NAME_PATTERN = "name_pattern"
    EVENT = "event"
    LOCATION = "location"
    TEMPORAL = "temporal"
    STATISTIC = "statistic"
    COMPARISON = "comparison"
    EXISTENCE = "existence"


@dataclass
class MockConstraint:
    """Mock constraint for testing."""

    id: str = "c1"
    value: str = "test constraint"
    weight: float = 1.0
    type: MockConstraintType = MockConstraintType.PROPERTY
    description: str = ""


@dataclass
class MockCandidate:
    """Mock candidate for testing."""

    name: str
    metadata: Dict[str, Any] = field(default_factory=dict)


class TestConstraintGuidedExplorerInit:
    """Tests for ConstraintGuidedExplorer initialization."""

    def test_init_with_defaults(self):
        """Initialize with default values."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()

        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        assert explorer.constraint_weight_threshold == 0.7
        assert explorer.early_validation is True

    def test_init_with_custom_weight_threshold(self):
        """Initialize with custom constraint weight threshold."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()

        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, constraint_weight_threshold=0.5
        )

        assert explorer.constraint_weight_threshold == 0.5

    def test_init_with_early_validation_disabled(self):
        """Initialize with early validation disabled."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()

        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, early_validation=False
        )

        assert explorer.early_validation is False

    def test_init_stores_model_and_engine(self):
        """Stores model and search engine references."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()

        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        assert explorer.model is mock_model
        assert explorer.search_engine is mock_engine


class TestPrioritizeConstraints:
    """Tests for _prioritize_constraints method."""

    def test_sorts_by_weight_descending(self):
        """Sorts constraints by weight in descending order."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="low",
                weight=0.3,
            ),
            Constraint(
                id="c2",
                type=ConstraintType.PROPERTY,
                description="",
                value="high",
                weight=0.9,
            ),
            Constraint(
                id="c3",
                type=ConstraintType.PROPERTY,
                description="",
                value="medium",
                weight=0.6,
            ),
        ]

        result = explorer._prioritize_constraints(constraints)

        assert result[0].value == "high"
        assert result[1].value == "medium"
        assert result[2].value == "low"

    def test_sorts_by_type_priority_when_weights_equal(self):
        """Sorts by type priority as secondary key when weights are equal."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        # Same weight, different types
        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="prop",
                weight=1.0,
            ),
            Constraint(
                id="c2",
                type=ConstraintType.NAME_PATTERN,
                description="",
                value="name",
                weight=1.0,
            ),
        ]

        result = explorer._prioritize_constraints(constraints)

        # With reverse=True sort and type_priority (NAME=1, PROPERTY=2),
        # PROPERTY (2) comes before NAME_PATTERN (1) when sorted descending
        assert result[0].type == ConstraintType.PROPERTY
        assert result[1].type == ConstraintType.NAME_PATTERN

    def test_handles_empty_constraints(self):
        """Handles empty constraints list."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        result = explorer._prioritize_constraints([])

        assert result == []


class TestGenerateConstraintQueries:
    """Tests for _generate_constraint_queries method."""

    def test_generates_base_constraint_query(self):
        """Generates basic query combining base and constraint."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="formed during ice age",
        )

        queries = explorer._generate_constraint_queries(
            constraint, "Colorado mountain trail"
        )

        # Should have base query with constraint value
        assert any("formed during ice age" in q for q in queries)

    def test_uses_entity_type_when_provided(self):
        """Uses entity type in query when provided."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="test property",
        )

        queries = explorer._generate_constraint_queries(
            constraint, "base query", entity_type="mountain trail"
        )

        assert any("mountain trail" in q for q in queries)


class TestNamePatternQueries:
    """Tests for _name_pattern_queries method."""

    def test_generates_body_part_queries(self):
        """Generates queries for body part name patterns."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="name contains body part",
        )

        queries = explorer._name_pattern_queries(
            constraint, "trail", entity_type=None
        )

        # Should generate queries with body part words
        assert len(queries) > 0
        body_parts = ["arm", "leg", "foot", "hand", "eye", "ear", "head"]
        assert any(
            any(part in q.lower() for part in body_parts) for q in queries
        )

    def test_uses_entity_type_for_body_parts(self):
        """Uses entity type when generating body part queries."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="body part in name",
        )

        queries = explorer._name_pattern_queries(
            constraint, "base", entity_type="mountain trail"
        )

        assert any("mountain trail" in q for q in queries)


class TestPropertyQueries:
    """Tests for _property_queries method."""

    def test_generates_property_query_variations(self):
        """Generates multiple property query variations."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value="ice age formation",
        )

        queries = explorer._property_queries(
            constraint, "trails", entity_type=None
        )

        assert len(queries) >= 2
        assert any("with" in q for q in queries)
        assert any("that" in q for q in queries)


class TestEventQueries:
    """Tests for _event_queries method."""

    def test_generates_event_queries_with_incident_keyword(self):
        """Generates event queries with incident/accident keywords."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.EVENT,
            description="fall event",
            value="deadly fall 2000-2021",
        )

        queries = explorer._event_queries(constraint, "trail", entity_type=None)

        assert any("incident" in q.lower() for q in queries)
        assert any("accident" in q.lower() for q in queries)


class TestLocationQueries:
    """Tests for _location_queries method."""

    def test_generates_location_queries(self):
        """Generates location-specific queries."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.LOCATION,
            description="location",
            value="Colorado",
        )

        queries = explorer._location_queries(
            constraint, "mountain trail", entity_type=None
        )

        assert any("Colorado" in q for q in queries)
        assert any("in Colorado" in q or "Colorado" in q for q in queries)


class TestCombineConstraintsQuery:
    """Tests for _combine_constraints_query method."""

    def test_combines_two_constraints(self):
        """Combines two constraints into one query."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="Colorado",
            ),
        ]

        query = explorer._combine_constraints_query("trail", constraints)

        assert query is not None
        assert "ice age" in query
        assert "Colorado" in query
        assert "AND" in query

    def test_returns_none_for_single_constraint(self):
        """Returns None when only one constraint provided."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
        ]

        query = explorer._combine_constraints_query("trail", constraints)

        assert query is None

    def test_returns_none_for_empty_constraints(self):
        """Returns None for empty constraints list."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        query = explorer._combine_constraints_query("trail", [])

        assert query is None


class TestQuickNameValidation:
    """Tests for _quick_name_validation method."""

    def test_validates_body_part_in_name(self):
        """Validates names containing body parts."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="contains body part",
        )

        assert (
            explorer._quick_name_validation("Heart Mountain", constraint)
            is True
        )
        assert (
            explorer._quick_name_validation("Arm Creek Trail", constraint)
            is True
        )
        assert explorer._quick_name_validation("Foot Lake", constraint) is True

    def test_rejects_names_without_body_part(self):
        """Rejects names without body parts."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="body part in name",
        )

        assert (
            explorer._quick_name_validation("Pine Mountain", constraint)
            is False
        )
        assert (
            explorer._quick_name_validation("Blue Lake Trail", constraint)
            is False
        )

    def test_accepts_non_body_part_constraints(self):
        """Accepts candidates for non-body-part constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        # Non-body-part name pattern
        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="other pattern",
            value="starts with letter",
        )

        # Should accept by default
        assert explorer._quick_name_validation("Any Name", constraint) is True


class TestEarlyValidateCandidates:
    """Tests for _early_validate_candidates method."""

    def test_validates_name_pattern_candidates(self):
        """Validates candidates for NAME_PATTERN constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="body part",
            value="body part in name",
        )

        candidates = [
            Candidate(name="Heart Mountain"),
            Candidate(name="Pine Trail"),
            Candidate(name="Arm Lake"),
        ]

        validated = explorer._early_validate_candidates(candidates, constraint)

        assert len(validated) == 2
        names = [c.name for c in validated]
        assert "Heart Mountain" in names
        assert "Arm Lake" in names
        assert "Pine Trail" not in names

    def test_skips_validation_for_non_name_pattern(self):
        """Skips validation for non-NAME_PATTERN constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="property",
            value="ice age formation",
        )

        candidates = [
            Candidate(name="Pine Trail"),
            Candidate(name="Oak Mountain"),
        ]

        validated = explorer._early_validate_candidates(candidates, constraint)

        # All candidates should pass through
        assert len(validated) == 2


class TestCrossConstraintExploration:
    """Tests for _cross_constraint_exploration method."""

    def test_requires_at_least_two_constraints(self):
        """Requires at least two constraints for cross-exploration."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="test",
            ),
        ]

        result = explorer._cross_constraint_exploration(
            constraints, "query", None
        )

        assert result == []


class TestExplore:
    """Tests for explore method."""

    def test_explore_returns_exploration_result(self):
        """Explore returns ExplorationResult."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
            ExplorationResult,
        )

        mock_model = Mock()
        mock_engine = Mock()
        mock_engine.run.return_value = []
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        result = explorer.explore("test query")

        assert isinstance(result, ExplorationResult)

    def test_explore_without_constraints_uses_basic(self):
        """Explore without constraints falls back to basic search."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
            ExplorationStrategy,
        )

        mock_model = Mock()
        mock_engine = Mock()
        mock_engine.run.return_value = []
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        result = explorer.explore("test query", constraints=None)

        # Falls back to basic strategy
        assert result.strategy_used == ExplorationStrategy.BREADTH_FIRST

    def test_explore_with_constraints_uses_constraint_guided(self):
        """Explore with constraints uses constraint-guided strategy."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
            ExplorationStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        mock_engine.run.return_value = []
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="test",
            ),
        ]

        result = explorer.explore("test query", constraints=constraints)

        assert result.strategy_used == ExplorationStrategy.CONSTRAINT_GUIDED

    def test_explore_records_metadata(self):
        """Explore records metadata about the exploration."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        mock_engine.run.return_value = []
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="test",
            ),
        ]

        result = explorer.explore("test query", constraints=constraints)

        assert "strategy" in result.metadata
        assert result.metadata["strategy"] == "constraint_guided"
        assert "early_validation" in result.metadata


class TestGenerateExplorationQueries:
    """Tests for generate_exploration_queries method."""

    def test_returns_base_query_without_constraints(self):
        """Returns base query when no constraints provided."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        queries = explorer.generate_exploration_queries("base query", [])

        assert queries == ["base query"]

    def test_generates_queries_from_constraints(self):
        """Generates queries from constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
        ]

        queries = explorer.generate_exploration_queries(
            "trail", [], constraints=constraints
        )

        assert len(queries) > 0
        assert any("ice age" in q for q in queries)

    def test_generates_combined_query_for_multiple_constraints(self):
        """Generates combined query when multiple constraints provided."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="Colorado",
            ),
        ]

        queries = explorer.generate_exploration_queries(
            "trail", [], constraints=constraints
        )

        # Should include combined query with AND
        assert any("AND" in q for q in queries)


class TestExploreWithConstraints:
    """Tests for explore method with various constraint configurations."""

    def test_explore_iterates_through_constraints(self):
        """Explore iterates through prioritized constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Candidate 1\nCandidate 2"
        )
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, max_search_time=10.0
        )

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="value1",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="value2",
            ),
        ]

        result = explorer.explore("test query", constraints=constraints)

        # Should have exploration paths from both constraints
        assert len(result.exploration_paths) > 0
        assert result.total_searched > 0

    def test_explore_skips_already_explored_queries(self):
        """Explore skips queries already explored."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Candidate 1")
        mock_engine = Mock()
        mock_engine.run.return_value = {"results": [], "query": "test"}
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        # Pre-populate explored queries
        explorer.explored_queries.add("test query ice age")
        explorer.explored_queries.add("test query with ice age")
        explorer.explored_queries.add("ice age test query")

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
        ]

        result = explorer.explore("test query", constraints=constraints)
        assert result is not None

    def test_explore_with_early_validation_disabled(self):
        """Explore without early validation."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Candidate 1\nCandidate 2"
        )
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, early_validation=False
        )

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.NAME_PATTERN,
                description="body part",
                value="body part in name",
            ),
        ]

        result = explorer.explore("test query", constraints=constraints)

        # Should not filter candidates when early_validation is False
        assert result is not None
        assert result.metadata["early_validation"] is False

    def test_explore_triggers_cross_constraint_exploration(self):
        """Explore triggers cross-constraint exploration with multiple constraints."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Candidate 1")
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, max_search_time=10.0
        )

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="Colorado",
            ),
        ]

        result = explorer.explore("trail", constraints=constraints)

        # Should include cross-constraint search in paths
        assert any(
            "Cross-constraint" in path for path in result.exploration_paths
        )


class TestConstraintTypeSpecificQueries:
    """Tests for constraint-type specific query generation."""

    def test_name_pattern_without_body_part(self):
        """Tests name pattern queries without body part keyword."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.NAME_PATTERN,
            description="pattern",
            value="starts with letter A",
        )

        queries = explorer._name_pattern_queries(
            constraint, "base", entity_type=None
        )

        # No body part queries for non-body-part patterns
        assert queries == []

    def test_event_constraint_queries(self):
        """Tests EVENT type constraint query generation."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.EVENT,
            description="event",
            value="fire",
        )

        queries = explorer._generate_constraint_queries(constraint, "forest")

        assert len(queries) > 1
        assert any("fire" in q for q in queries)
        assert any("incident" in q.lower() for q in queries)

    def test_location_constraint_queries(self):
        """Tests LOCATION type constraint query generation."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraint = Constraint(
            id="c1",
            type=ConstraintType.LOCATION,
            description="location",
            value="California",
        )

        queries = explorer._generate_constraint_queries(constraint, "parks")

        assert len(queries) > 1
        assert any("California" in q for q in queries)


class TestCrossConstraintExplorationExtended:
    """Extended tests for _cross_constraint_exploration method."""

    def test_executes_search_for_combined_constraints(self):
        """Executes search for combined constraint query."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Candidate 1")
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="Colorado",
            ),
        ]

        result = explorer._cross_constraint_exploration(
            constraints, "trail", entity_type=None
        )

        # Should execute search and return candidates
        mock_engine.run.assert_called()
        assert isinstance(result, list)

    def test_skips_already_explored_combined_query(self):
        """Skips cross-constraint exploration if query already explored."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="",
                value="ice age",
            ),
            Constraint(
                id="c2",
                type=ConstraintType.LOCATION,
                description="",
                value="Colorado",
            ),
        ]

        # Pre-add the combined query to explored
        explorer.explored_queries.add("trail ice age and colorado")

        result = explorer._cross_constraint_exploration(
            constraints, "trail", entity_type=None
        )

        # Should return empty since query already explored
        assert result == []


class TestRankByConstraintAlignment:
    """Tests for _rank_by_constraint_alignment method."""

    def test_scores_candidates_by_constraint_alignment(self):
        """Scores candidates based on constraint alignment."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.NAME_PATTERN,
                description="body part",
                value="body part in name",
                weight=1.0,
            ),
        ]

        candidates = [
            Candidate(name="Heart Mountain"),  # Has body part
            Candidate(name="Pine Trail"),  # No body part
            Candidate(name="Arm Lake"),  # Has body part
        ]

        ranked = explorer._rank_by_constraint_alignment(
            candidates, constraints, "trail"
        )

        # Candidates with body parts should be scored higher
        assert len(ranked) == 3
        # Check that constraint_alignment_score was set
        for c in ranked:
            assert hasattr(c, "constraint_alignment_score")

    def test_handles_empty_candidates(self):
        """Handles empty candidate list."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.NAME_PATTERN,
                description="body part",
                value="body part",
            ),
        ]

        ranked = explorer._rank_by_constraint_alignment(
            [], constraints, "query"
        )

        assert ranked == []

    def test_handles_non_name_pattern_constraints(self):
        """Handles constraints that aren't NAME_PATTERN."""
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )

        mock_model = Mock()
        mock_engine = Mock()
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        constraints = [
            Constraint(
                id="c1",
                type=ConstraintType.PROPERTY,
                description="property",
                value="formed during ice age",
            ),
        ]

        candidates = [
            Candidate(name="Trail A"),
            Candidate(name="Trail B"),
        ]

        ranked = explorer._rank_by_constraint_alignment(
            candidates, constraints, "trail"
        )

        # Should still return candidates, just with 0 alignment score
        assert len(ranked) == 2


class TestBasicExploration:
    """Tests for _basic_exploration fallback method."""

    def test_basic_exploration_returns_result(self):
        """Basic exploration returns proper result."""
        import time
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Candidate 1")
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(mock_model, mock_engine)

        result = explorer._basic_exploration("query", "entity", time.time())

        assert result is not None
        assert result.total_searched == 1
        assert result.metadata["strategy"] == "basic_fallback"

    def test_basic_exploration_limits_candidates(self):
        """Basic exploration respects max_candidates."""
        import time
        from local_deep_research.advanced_search_system.candidate_exploration.constraint_guided_explorer import (
            ConstraintGuidedExplorer,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="C1\nC2\nC3\nC4\nC5\nC6\nC7\nC8\nC9\nC10"
        )
        mock_engine = Mock()
        mock_engine.run.return_value = {
            "results": [{"title": "Result", "snippet": "Content"}],
            "query": "test",
        }
        explorer = ConstraintGuidedExplorer(
            mock_model, mock_engine, max_candidates=3
        )

        result = explorer._basic_exploration("query", None, time.time())

        assert len(result.candidates) <= 3
