"""
High-value pure logic tests for SmartQueryStrategy.

Tests focus on _build_standard_query() and _should_use_entity_seeding()
without requiring LLM calls or external dependencies.
"""

from unittest.mock import patch


from local_deep_research.advanced_search_system.constraints.base_constraint import (
    Constraint,
    ConstraintType,
)
from local_deep_research.advanced_search_system.strategies.smart_query_strategy import (
    SmartQueryStrategy,
)


def _make_strategy(**attrs):
    """Create a SmartQueryStrategy with __init__ bypassed and attributes set."""
    with patch.object(
        SmartQueryStrategy, "__init__", lambda self, *a, **kw: None
    ):
        strategy = SmartQueryStrategy()
    strategy.searched_queries = set()
    strategy.query_variations = set()
    strategy.entity_type = "unknown entity"
    for key, val in attrs.items():
        setattr(strategy, key, val)
    return strategy


def _make_constraint(
    value, weight=1.0, ctype=ConstraintType.PROPERTY, cid=None
):
    """Helper to build a Constraint with sensible defaults."""
    return Constraint(
        id=cid or f"c_{value[:8]}",
        type=ctype,
        description=f"desc for {value}",
        value=value,
        weight=weight,
    )


class TestBuildStandardQueryQuoting:
    """Tests for multi-word quoting behaviour in _build_standard_query."""

    def test_multi_word_term_gets_quoted(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("blue eyes", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert '"blue eyes"' in result

    def test_single_word_term_not_quoted(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("tall", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert '"tall"' not in result
        assert "tall" in result

    def test_already_quoted_term_stays_quoted(self):
        strategy = _make_strategy()
        constraints = [_make_constraint('"ice age"', weight=0.9)]
        result = strategy._build_standard_query(constraints)
        # Should not get double-quoted
        assert '""ice age""' not in result
        assert '"ice age"' in result

    def test_term_with_internal_space_but_already_quoted(self):
        strategy = _make_strategy()
        constraints = [_make_constraint('"dark knight"', weight=0.5)]
        result = strategy._build_standard_query(constraints)
        assert '"dark knight"' in result
        assert '""' not in result


class TestBuildStandardQueryWeightSplitting:
    """Tests for critical vs supplementary term splitting based on weight."""

    def test_high_weight_treated_as_critical(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("stretchy", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert "stretchy" in result

    def test_low_weight_treated_as_supplementary(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("optional trait", weight=0.3)]
        result = strategy._build_standard_query(constraints)
        assert '"optional trait"' in result

    def test_weight_exactly_0_7_is_supplementary(self):
        """Weight must be strictly > 0.7 to be critical."""
        strategy = _make_strategy()
        critical = _make_constraint("important", weight=0.8)
        borderline = _make_constraint("borderline", weight=0.7)
        # Both should appear, but order reflects critical first then supplementary
        result = strategy._build_standard_query([critical, borderline])
        idx_important = result.index("important")
        idx_borderline = result.index("borderline")
        assert idx_important < idx_borderline

    def test_weight_0_71_is_critical(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("just above", weight=0.71)]
        result = strategy._build_standard_query(constraints)
        assert '"just above"' in result

    def test_only_first_two_supplementary_terms_included(self):
        strategy = _make_strategy()
        constraints = [
            _make_constraint("supp1", weight=0.3),
            _make_constraint("supp2", weight=0.4),
            _make_constraint("supp3", weight=0.5),
        ]
        result = strategy._build_standard_query(constraints)
        assert "supp1" in result
        assert "supp2" in result
        assert "supp3" not in result

    def test_many_supplementary_only_two_included(self):
        strategy = _make_strategy()
        constraints = [
            _make_constraint(f"supp{i}", weight=0.2) for i in range(5)
        ]
        result = strategy._build_standard_query(constraints)
        parts = result.split()
        # Only supp0 and supp1 should appear
        assert "supp0" in parts
        assert "supp1" in parts
        assert "supp2" not in parts

    def test_critical_and_supplementary_mixed(self):
        strategy = _make_strategy()
        constraints = [
            _make_constraint("crit1", weight=0.9),
            _make_constraint("crit2", weight=0.8),
            _make_constraint("sup1", weight=0.5),
            _make_constraint("sup2", weight=0.3),
            _make_constraint("sup3", weight=0.1),
        ]
        result = strategy._build_standard_query(constraints)
        assert "crit1" in result
        assert "crit2" in result
        assert "sup1" in result
        assert "sup2" in result
        assert "sup3" not in result


class TestBuildStandardQueryEntityType:
    """Tests for entity_type inclusion in _build_standard_query."""

    def test_entity_type_added_when_known(self):
        strategy = _make_strategy(entity_type="superhero")
        constraints = [_make_constraint("stretchy", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert result.startswith("superhero")

    def test_entity_type_not_added_when_unknown(self):
        strategy = _make_strategy(entity_type="unknown entity")
        constraints = [_make_constraint("stretchy", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert "unknown entity" not in result

    def test_entity_type_not_added_when_none(self):
        strategy = _make_strategy(entity_type=None)
        constraints = [_make_constraint("stretchy", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert result == "stretchy"

    def test_entity_type_not_added_when_missing(self):
        strategy = _make_strategy()
        # Remove entity_type entirely
        if hasattr(strategy, "entity_type"):
            delattr(strategy, "entity_type")
        constraints = [_make_constraint("stretchy", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert result == "stretchy"

    def test_entity_type_appears_before_constraint_terms(self):
        strategy = _make_strategy(entity_type="anime character")
        constraints = [_make_constraint("fire powers", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert result.startswith("anime character")


class TestBuildStandardQueryEdgeCases:
    """Edge cases for _build_standard_query."""

    def test_empty_constraints(self):
        strategy = _make_strategy()
        result = strategy._build_standard_query([])
        assert result == ""

    def test_single_constraint(self):
        strategy = _make_strategy()
        constraints = [_make_constraint("elastic body", weight=0.9)]
        result = strategy._build_standard_query(constraints)
        assert '"elastic body"' in result

    def test_all_parts_joined_with_spaces(self):
        strategy = _make_strategy(entity_type="person")
        constraints = [
            _make_constraint("tall", weight=0.9),
            _make_constraint("brown hair", weight=0.5),
        ]
        result = strategy._build_standard_query(constraints)
        # Should be space-joined, no double spaces
        assert "  " not in result
        parts = result.split(" ")
        assert parts[0] == "person"
        assert "tall" in parts


class TestShouldUseEntitySeeding:
    """Tests for _should_use_entity_seeding logic."""

    def test_character_returns_true(self):
        strategy = _make_strategy(entity_type="anime character")
        assert strategy._should_use_entity_seeding() is True

    def test_person_returns_true(self):
        strategy = _make_strategy(entity_type="famous person")
        assert strategy._should_use_entity_seeding() is True

    def test_hero_returns_true(self):
        strategy = _make_strategy(entity_type="superhero")
        assert strategy._should_use_entity_seeding() is True

    def test_case_insensitive_character(self):
        strategy = _make_strategy(entity_type="VIDEO GAME CHARACTER")
        assert strategy._should_use_entity_seeding() is True

    def test_case_insensitive_person(self):
        strategy = _make_strategy(entity_type="PERSON")
        assert strategy._should_use_entity_seeding() is True

    def test_case_insensitive_hero(self):
        strategy = _make_strategy(entity_type="HERO")
        assert strategy._should_use_entity_seeding() is True

    def test_location_returns_false(self):
        strategy = _make_strategy(entity_type="location")
        assert strategy._should_use_entity_seeding() is False

    def test_unknown_returns_false(self):
        strategy = _make_strategy(entity_type="unknown entity")
        assert strategy._should_use_entity_seeding() is False

    def test_empty_string_returns_false(self):
        strategy = _make_strategy(entity_type="")
        assert strategy._should_use_entity_seeding() is False

    def test_event_returns_false(self):
        strategy = _make_strategy(entity_type="historical event")
        assert strategy._should_use_entity_seeding() is False

    def test_no_entity_type_attr_returns_false(self):
        strategy = _make_strategy()
        if hasattr(strategy, "entity_type"):
            delattr(strategy, "entity_type")
        assert strategy._should_use_entity_seeding() is False
