"""Concept overlay: synonym expansion + rivalry exclusion.

Pre-2026-05-01: data lived as Python frozensets, tests called the
helpers directly. Post-2026-05-01: data lives in per-shard SQLite
tables, the helpers walk shards via shards_dir. Each test seeds
a temporary shard dir using the legacy seed so the assertions stay
identical.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from aborist.concepts import invalidate_cache as _invalidate_cache
from aborist.concepts.query import invalidate_cache
from aborist.concepts.seed import seed_legacy_concepts
from aborist.qa.concepts import (
    has_compare_phrasing,
    rivalry_excluded,
    synonym_expand,
)
from aborist.store import connect


# ---------------------------------------------------------------------------
# Fixture: a tmp shards_dir containing one shard pre-seeded with legacy data.
# ---------------------------------------------------------------------------


@pytest.fixture
def seeded_shards(tmp_path: Path) -> Path:
    shards_dir = tmp_path / "shards"
    shards_dir.mkdir()
    shard_path = shards_dir / "000.db"
    conn = connect(shard_path)
    seed_legacy_concepts(conn)
    conn.close()
    invalidate_cache()
    return shards_dir


# ---------------------------------------------------------------------------
# synonym_expand
# ---------------------------------------------------------------------------


def test_synonym_expand_amd_pulls_athlon_and_back(seeded_shards):
    expanded = synonym_expand({"athlon"}, shards_dir=seeded_shards)
    assert "amd" in expanded
    assert "duron" in expanded
    assert "thunderbird" in expanded

    expanded = synonym_expand({"amd"}, shards_dir=seeded_shards)
    assert "athlon" in expanded


def test_synonym_expand_unrelated_token_unchanged(seeded_shards):
    expanded = synonym_expand({"banana"}, shards_dir=seeded_shards)
    assert expanded == {"banana"}


def test_synonym_expand_doesnt_cross_groups(seeded_shards):
    """AMD and Intel are in different groups — expanding one shouldn't
    pull in the other."""
    expanded = synonym_expand({"amd"}, shards_dir=seeded_shards)
    assert "intel" not in expanded
    assert "pentium" not in expanded


def test_synonym_expand_no_shards_dir_is_noop():
    """Without a shards_dir, expand returns the input unchanged.
    Same degenerate behavior the legacy frozenset code had when
    a query token didn't hit any group."""
    assert synonym_expand({"athlon"}) == {"athlon"}


# ---------------------------------------------------------------------------
# rivalry_excluded
# ---------------------------------------------------------------------------


def test_rivalry_excluded_amd_query_drops_intel(seeded_shards):
    excluded = rivalry_excluded({"amd"}, compare_phrasing=False, shards_dir=seeded_shards)
    assert "intel" in excluded
    assert "pentium" in excluded


def test_rivalry_excluded_intel_query_drops_amd(seeded_shards):
    excluded = rivalry_excluded({"intel"}, compare_phrasing=False, shards_dir=seeded_shards)
    assert "amd" in excluded
    assert "athlon" in excluded


def test_rivalry_excluded_both_sides_no_exclusion(seeded_shards):
    excluded = rivalry_excluded(
        {"amd", "intel"}, compare_phrasing=False, shards_dir=seeded_shards
    )
    assert excluded == set()


def test_rivalry_excluded_compare_phrasing_suppresses(seeded_shards):
    excluded = rivalry_excluded(
        {"amd"}, compare_phrasing=True, shards_dir=seeded_shards
    )
    assert excluded == set()


def test_rivalry_excluded_no_shards_dir_is_noop():
    """Without shards_dir, no exclusion (caller hasn't loaded the
    concept layer)."""
    assert rivalry_excluded({"amd"}, compare_phrasing=False) == set()


# ---------------------------------------------------------------------------
# Compare-phrasing detection (independent of shards)
# ---------------------------------------------------------------------------


def test_compare_phrasing_detection():
    assert has_compare_phrasing("compare AMD and Intel")
    assert has_compare_phrasing("AMD vs Intel")
    assert has_compare_phrasing("difference between Mac and Windows")
    assert not has_compare_phrasing("what is the fastest AMD CPU?")
    assert not has_compare_phrasing("tell me about Athlon")


def test_mac_windows_rivalry(seeded_shards):
    """Independent rivalry pair — Mac vs Windows."""
    excluded = rivalry_excluded(
        {"macintosh"}, compare_phrasing=False, shards_dir=seeded_shards
    )
    assert "windows" in excluded

    excluded = rivalry_excluded(
        {"windows"}, compare_phrasing=False, shards_dir=seeded_shards
    )
    assert "macintosh" in excluded
    assert "macos" in excluded


# ---------------------------------------------------------------------------
# Brain-tech group (added 2026-05-01)
# ---------------------------------------------------------------------------


def test_brain_tech_synonym_group_includes_telepathy_and_thoughts(seeded_shards):
    """Run 1 retrieval-fix group: 'thoughts' should pull 'telepathy',
    'neurotechnology', etc. so the title-relevance accept path admits
    brain-tech pages."""
    expanded = synonym_expand({"thoughts"}, shards_dir=seeded_shards)
    assert "telepathy" in expanded
    assert "neurotechnology" in expanded
    assert "mind" in expanded


# ---------------------------------------------------------------------------
# Cache invalidation
# ---------------------------------------------------------------------------


def test_invalidate_cache_does_not_break_lookup(seeded_shards):
    """After explicit cache invalidation, a fresh load reproduces
    the same answers."""
    expanded_1 = synonym_expand({"amd"}, shards_dir=seeded_shards)
    invalidate_cache()
    expanded_2 = synonym_expand({"amd"}, shards_dir=seeded_shards)
    assert expanded_1 == expanded_2


def test_export_invalidate_cache_alias(seeded_shards):
    """The package-level ``invalidate_cache`` is the same callable as
    ``aborist.concepts.query.invalidate_cache`` — exposed at top level
    for callers that don't want to import the implementation module."""
    assert _invalidate_cache is invalidate_cache
