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

Covers:
- entity_patterns dict completeness
- _identify_entity_type() keyword matching and fallback logic
- _generate_entity_searches() type-specific and constraint-based queries
"""

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.browsecomp_entity_strategy import (
    BrowseCompEntityStrategy,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

ENTITY_PATTERNS = {
    "company": [
        "company",
        "corporation",
        "group",
        "firm",
        "business",
        "conglomerate",
    ],
    "person": ["person", "individual", "character", "figure", "people"],
    "event": [
        "event",
        "incident",
        "occurrence",
        "game",
        "match",
        "competition",
    ],
    "location": [
        "place",
        "location",
        "city",
        "country",
        "region",
        "area",
    ],
    "product": ["product", "item", "device", "software", "app", "tool"],
}


def _make_strategy():
    """Create a BrowseCompEntityStrategy with __init__ bypassed."""
    with patch.object(
        BrowseCompEntityStrategy, "__init__", lambda self, *a, **kw: None
    ):
        strategy = BrowseCompEntityStrategy()
    strategy.entity_patterns = {k: list(v) for k, v in ENTITY_PATTERNS.items()}
    return strategy


def _make_constraint(
    constraint_type, value, cid="c1", description="test", weight=1.0
):
    """Create a Constraint with minimal boilerplate."""
    return Constraint(
        id=cid,
        type=constraint_type,
        description=description,
        value=value,
        weight=weight,
    )


# ---------------------------------------------------------------------------
# entity_patterns dict
# ---------------------------------------------------------------------------


class TestEntityPatternsDict:
    """Verify entity_patterns contains all 5 entity types with correct keywords."""

    def test_has_all_five_entity_types(self):
        strategy = _make_strategy()
        assert set(strategy.entity_patterns.keys()) == {
            "company",
            "person",
            "event",
            "location",
            "product",
        }

    def test_company_keywords(self):
        strategy = _make_strategy()
        expected = {
            "company",
            "corporation",
            "group",
            "firm",
            "business",
            "conglomerate",
        }
        assert set(strategy.entity_patterns["company"]) == expected

    def test_person_keywords(self):
        strategy = _make_strategy()
        expected = {"person", "individual", "character", "figure", "people"}
        assert set(strategy.entity_patterns["person"]) == expected

    def test_event_keywords(self):
        strategy = _make_strategy()
        expected = {
            "event",
            "incident",
            "occurrence",
            "game",
            "match",
            "competition",
        }
        assert set(strategy.entity_patterns["event"]) == expected

    def test_location_keywords(self):
        strategy = _make_strategy()
        expected = {"place", "location", "city", "country", "region", "area"}
        assert set(strategy.entity_patterns["location"]) == expected

    def test_product_keywords(self):
        strategy = _make_strategy()
        expected = {"product", "item", "device", "software", "app", "tool"}
        assert set(strategy.entity_patterns["product"]) == expected


# ---------------------------------------------------------------------------
# _identify_entity_type
# ---------------------------------------------------------------------------


class TestIdentifyEntityType:
    """Test _identify_entity_type() keyword matching and fallback branches."""

    # -- Direct keyword matches --

    def test_company_keyword_corporation(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which corporation merged?")
            == "company"
        )

    def test_company_keyword_business(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("What business failed?") == "company"
        )

    def test_company_keyword_conglomerate(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Name the conglomerate") == "company"
        )

    def test_person_keyword_individual(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which individual won?") == "person"
        )

    def test_person_keyword_character(self):
        strategy = _make_strategy()
        assert strategy._identify_entity_type("Name the character") == "person"

    def test_event_keyword_competition(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which competition was held?")
            == "event"
        )

    def test_event_keyword_game(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("What game was played?") == "event"
        )

    def test_location_keyword_city(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which city hosted it?")
            == "location"
        )

    def test_product_keyword_software(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which software was released?")
            == "product"
        )

    def test_case_insensitive_matching(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("THE CORPORATION announced")
            == "company"
        )

    # -- Fallback branches (no keyword match) --

    def test_fallback_who_returns_person(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Who discovered penicillin?")
            == "person"
        )

    def test_fallback_which_returns_product(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Which one was recalled?")
            == "product"
        )

    def test_fallback_what_plus_company_returns_company(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("What company went bankrupt?")
            == "company"
        )

    def test_fallback_else_returns_entity(self):
        strategy = _make_strategy()
        assert (
            strategy._identify_entity_type("Identify the unknown xyz")
            == "entity"
        )

    def test_empty_query_returns_entity(self):
        strategy = _make_strategy()
        assert strategy._identify_entity_type("") == "entity"

    # -- First-match priority --

    def test_first_type_wins_when_multiple_keywords_present(self):
        """entity_patterns is iterated in dict insertion order; company comes first."""
        strategy = _make_strategy()
        # Contains both "company" (company) and "person" (person)
        result = strategy._identify_entity_type("company person")
        assert result == "company"


# ---------------------------------------------------------------------------
# _generate_entity_searches
# ---------------------------------------------------------------------------


class TestGenerateEntitySearches:
    """Test _generate_entity_searches() base queries and constraint expansion."""

    # -- Base queries by entity type --

    def test_company_base_queries(self):
        strategy = _make_strategy()
        searches = strategy._generate_entity_searches("company", [])
        assert len(searches) == 3
        assert any("companies" in s for s in searches)
        assert any("corporation" in s for s in searches)

    def test_person_base_queries(self):
        strategy = _make_strategy()
        searches = strategy._generate_entity_searches("person", [])
        assert len(searches) == 3
        assert any("people" in s for s in searches)

    def test_event_base_queries(self):
        strategy = _make_strategy()
        searches = strategy._generate_entity_searches("event", [])
        assert len(searches) == 3
        assert any("events" in s for s in searches)

    def test_unknown_type_returns_empty_base(self):
        strategy = _make_strategy()
        searches = strategy._generate_entity_searches("entity", [])
        assert searches == []

    # -- Constraint type matching uses constraint.type.value (e.g. "temporal")
    #    compared against uppercase strings ("TEMPORAL") in the source code,
    #    so the branches currently never fire. We test both the actual behavior
    #    and document what *would* happen with a case-matched custom constraint.

    # -- TEMPORAL constraints (actual enum value is lowercase "temporal") --

    def test_temporal_constraint_enum_value_is_lowercase(self):
        """ConstraintType.TEMPORAL.value is 'temporal', not 'TEMPORAL'."""
        assert ConstraintType.TEMPORAL.value == "temporal"

    def test_temporal_constraint_no_match_due_to_case(self):
        """Source compares .value == 'TEMPORAL' but enum value is 'temporal',
        so TEMPORAL constraints do NOT add year searches with the standard enum."""
        strategy = _make_strategy()
        constraint = _make_constraint(
            ConstraintType.TEMPORAL, "Founded in 1995"
        )
        searches = strategy._generate_entity_searches("company", [constraint])
        # Only the 3 base queries -- the TEMPORAL branch is dead code
        assert len(searches) == 3

    def test_temporal_constraint_no_year_adds_nothing(self):
        strategy = _make_strategy()
        constraint = _make_constraint(ConstraintType.TEMPORAL, "recently")
        searches = strategy._generate_entity_searches("event", [constraint])
        # Only 3 base queries
        assert len(searches) == 3

    # -- LOCATION constraints (actual enum value is lowercase "location") --

    def test_location_constraint_no_match_due_to_case(self):
        """Source compares .value == 'LOCATION' but enum value is 'location',
        so LOCATION constraints do NOT add place searches with the standard enum."""
        strategy = _make_strategy()
        constraint = _make_constraint(ConstraintType.LOCATION, "in New York")
        searches = strategy._generate_entity_searches("company", [constraint])
        assert len(searches) == 3

    def test_location_constraint_no_proper_noun_adds_nothing(self):
        strategy = _make_strategy()
        constraint = _make_constraint(ConstraintType.LOCATION, "in the north")
        searches = strategy._generate_entity_searches("company", [constraint])
        assert len(searches) == 3

    # -- STATISTIC constraints (actual enum value is lowercase "statistic") --

    def test_statistic_constraint_no_match_due_to_case(self):
        """Source compares .value == 'STATISTIC' but enum value is 'statistic',
        so STATISTIC constraints do NOT add number searches with the standard enum."""
        strategy = _make_strategy()
        constraint = _make_constraint(
            ConstraintType.STATISTIC, "revenue of 500 million"
        )
        searches = strategy._generate_entity_searches("company", [constraint])
        assert len(searches) == 3

    # -- Constraint matching with a mock type whose .value IS uppercase --
    #    This verifies the branch logic *would* work if the case matched.

    def test_temporal_branch_fires_with_uppercase_value(self):
        """When constraint.type.value literally equals 'TEMPORAL', years are extracted."""
        strategy = _make_strategy()
        mock_type = type("MockType", (), {"value": "TEMPORAL"})()
        constraint = _make_constraint(
            ConstraintType.TEMPORAL, "Founded in 1995"
        )
        constraint.type = mock_type
        searches = strategy._generate_entity_searches("company", [constraint])
        assert "company 1995" in searches

    def test_location_branch_fires_with_uppercase_value(self):
        """When constraint.type.value literally equals 'LOCATION', locations are extracted."""
        strategy = _make_strategy()
        mock_type = type("MockType", (), {"value": "LOCATION"})()
        constraint = _make_constraint(ConstraintType.LOCATION, "in New York")
        constraint.type = mock_type
        searches = strategy._generate_entity_searches("company", [constraint])
        assert any("New York" in s for s in searches)

    def test_statistic_branch_fires_with_uppercase_value(self):
        """When constraint.type.value literally equals 'STATISTIC', numbers are extracted."""
        strategy = _make_strategy()
        mock_type = type("MockType", (), {"value": "STATISTIC"})()
        constraint = _make_constraint(
            ConstraintType.STATISTIC, "revenue of 500 million"
        )
        constraint.type = mock_type
        searches = strategy._generate_entity_searches("company", [constraint])
        assert "company 500" in searches

    def test_multiple_uppercase_constraints_accumulate(self):
        """Multiple constraint branches fire when type.value is uppercase."""
        strategy = _make_strategy()
        temporal = _make_constraint(
            ConstraintType.TEMPORAL, "in 2020", cid="c1"
        )
        temporal.type = type("MockType", (), {"value": "TEMPORAL"})()
        location = _make_constraint(
            ConstraintType.LOCATION, "in Paris", cid="c2"
        )
        location.type = type("MockType", (), {"value": "LOCATION"})()
        searches = strategy._generate_entity_searches(
            "company", [temporal, location]
        )
        assert "company 2020" in searches
        assert any("Paris" in s for s in searches)

    # -- Empty constraints --

    def test_empty_constraints_returns_only_base(self):
        strategy = _make_strategy()
        searches = strategy._generate_entity_searches("person", [])
        assert len(searches) == 3

    # -- Non-matching constraint type passes through silently --

    def test_property_constraint_adds_nothing(self):
        """PROPERTY type does not match any branch, so no extra searches."""
        strategy = _make_strategy()
        constraint = _make_constraint(ConstraintType.PROPERTY, "is very large")
        searches = strategy._generate_entity_searches("company", [constraint])
        assert len(searches) == 3
