"""S4 dead-code wiring tests — source-level + behavioural verification.

Items covered:
  - Item 1: record_search_latency wired into search_post_scoring
  - Item 2: EXPANDED query text visible in briefing lines (not buried in all_hits_flat)
  - Item 3: _resolve_superseded_chain called in search_scoring._score_item
  - Item 4: _render_items dead code removed from briefing

All tests pass without a running DB / brain daemon (safe for CI).
"""
import inspect
import sys
import types
from pathlib import Path
from unittest.mock import patch, MagicMock

ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))


# ---------------------------------------------------------------------------
# Item 1: record_search_latency wired into search_post_scoring
# ---------------------------------------------------------------------------

def test_post_scoring_imports_record_search_latency():
    """apply_post_scoring_impl must import and call record_search_latency."""
    import pulse.brain.brain_search.search_post_scoring as mod
    src = inspect.getsource(mod.apply_post_scoring_impl)
    assert "record_search_latency" in src, (
        "apply_post_scoring_impl does not call record_search_latency — Item 157 unwired"
    )


def test_post_scoring_latency_import_guarded():
    """record_search_latency import must be in a try/except (fail-open)."""
    import pulse.brain.brain_search.search_post_scoring as mod
    src = inspect.getsource(mod.apply_post_scoring_impl)
    assert "from pulse.brain.brain_search.search_scoring_p36 import record_search_latency" in src, (
        "record_search_latency not imported from search_scoring_p36 in post_scoring"
    )
    # Verify it's wrapped in try/except
    assert "except Exception: pass" in src, (
        "record_search_latency call is not fail-open (missing except Exception: pass)"
    )


def test_post_scoring_measures_elapsed():
    """apply_post_scoring_impl must compute elapsed_ms using _time_ps.time()."""
    import pulse.brain.brain_search.search_post_scoring as mod
    src = inspect.getsource(mod.apply_post_scoring_impl)
    assert "_ps_start" in src, (
        "apply_post_scoring_impl does not capture start time (_ps_start missing)"
    )
    assert "elapsed_ms" in src, (
        "apply_post_scoring_impl does not compute elapsed_ms"
    )


def test_record_search_latency_callable():
    """record_search_latency must be importable and accept a float."""
    from pulse.brain.brain_search.search_scoring_p36 import record_search_latency
    # Should not raise for any float input
    record_search_latency(0.123)
    record_search_latency(0.0)


def test_post_scoring_latency_actually_recorded():
    """Behavioural: after apply_post_scoring_impl runs, latency window should have entries."""
    from pulse.brain.brain_search.search_scoring_p36 import _LATENCY_WINDOW, record_search_latency
    from pulse.brain.brain_search.search_post_scoring import apply_post_scoring_impl
    _LATENCY_WINDOW.clear()

    results = {"wins": [{"text": "test", "confidence": 0.8, "salience": 1.0}]}
    apply_post_scoring_impl(
        results, "test query", "general",
        lambda r, q: None,
        lambda r: None,
        lambda r, q: None,
        None,
    )
    assert len(_LATENCY_WINDOW) >= 1, (
        "record_search_latency was not called — _LATENCY_WINDOW still empty after post_scoring"
    )
    assert _LATENCY_WINDOW[-1] >= 0, "Recorded latency must be non-negative"


# ---------------------------------------------------------------------------
# Item 2: EXPANDED query text visible in briefing lines
# ---------------------------------------------------------------------------

def test_briefing_captures_expansions_variable():
    """format_briefing must capture expansion terms in _expansions variable."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert "_expansions" in src, (
        "format_briefing does not use _expansions variable — Item 90 expansion capture missing"
    )


def test_briefing_renders_expanded_to_lines():
    """format_briefing must render EXPANDED directly to lines list."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert 'lines.append' in src and 'EXPANDED' in src, (
        "format_briefing does not append EXPANDED text to lines — still dead code"
    )
    # Verify the EXPANDED line is appended after lines = []
    lines_init_pos = src.index("lines = []")
    expanded_pos = src.index("EXPANDED: query enriched with")
    assert expanded_pos > lines_init_pos, (
        "EXPANDED rendering must appear after 'lines = []' initialisation"
    )


def test_briefing_expansion_not_injected_into_all_hits_flat():
    """_expansion sentinel must NOT be inserted into all_hits_flat (no false avg_conf inflation)."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    # The old pattern was: all_hits_flat.insert(0, {..., "_expansion": True})
    # This must be gone
    assert 'all_hits_flat.insert(0' not in src, (
        "EXPANDED item is still being injected into all_hits_flat — pollutes avg_conf"
    )


def test_briefing_expansion_renders_in_output():
    """Behavioural: when graph returns expansion terms, they appear in briefing output."""
    import pulse.brain.brain_query.briefing as mod

    fake_conn = MagicMock()
    fake_conn.execute.return_value.fetchall.return_value = [("async_pattern",), ("event_loop",)]

    fake_results = {
        "query": "python async",
        "total_results": 1,
        "elapsed_ms": 12,
        "results": {
            "wins": [{"text": "use asyncio properly", "confidence": 0.85,
                      "salience": 1.0, "source": "wins"}]
        },
    }

    with patch("pulse.core.brain_db.get_conn", return_value=fake_conn):
        output = mod.format_briefing("python async", fake_results)

    assert "EXPANDED" in output, (
        f"EXPANDED enrichment not visible in briefing output.\nOutput was:\n{output}"
    )


# ---------------------------------------------------------------------------
# Item 3: _resolve_superseded_chain wired into _score_item
# ---------------------------------------------------------------------------

def test_score_item_calls_resolve_superseded_chain():
    """_score_item must import and call _resolve_superseded_chain for superseded items."""
    import pulse.brain.brain_search.search_scoring as mod
    src = inspect.getsource(mod._score_item)
    assert "_resolve_superseded_chain" in src, (
        "_score_item does not call _resolve_superseded_chain — tombstone chains never followed"
    )


def test_score_item_resolve_guarded_try_except():
    """_resolve_superseded_chain call must be fail-open."""
    import pulse.brain.brain_search.search_scoring as mod
    src = inspect.getsource(mod._score_item)
    assert "from pulse.brain.plasticity.reconsolidation import _resolve_superseded_chain" in src, (
        "_resolve_superseded_chain not imported from pulse.brain.reconsolidation in _score_item"
    )
    assert "except Exception: pass" in src, (
        "_resolve_superseded_chain call is not wrapped in try/except"
    )


def test_score_item_annotates_resolved_to():
    """Behavioural: superseded item with a chain should get _resolved_to annotation."""
    import pulse.brain.brain_search.search_scoring as mod
    from pulse.core.temporal import temporal_confidence, temporal_salience
    from pulse.core.uncertainty import wilson_interval

    item = {
        "id": "old-id-123",
        "validity": "superseded",
        "title": "old title",
        "confidence": 0.7,
        "salience": 1.0,
        "type": "lesson",
    }

    mock_resolve = MagicMock(return_value="new-id-456")

    with patch("pulse.brain.plasticity.reconsolidation._resolve_superseded_chain", mock_resolve):
        result = mod._score_item(
            item, "lessons", "test query", "general",
            set(), None, {}, {},
            temporal_confidence, temporal_salience, wilson_interval,
        )

    # Item should not be None (superseded stays searchable, just marked)
    assert result is not None, "_score_item filtered out a superseded item entirely"
    assert result.get("_resolved_to") == "new-id-456", (
        f"_resolved_to not set on superseded item. Item: {result}"
    )


def test_score_item_no_resolved_to_when_chain_unchanged():
    """When resolved ID equals original, _resolved_to must NOT be set."""
    import pulse.brain.brain_search.search_scoring as mod
    from pulse.core.temporal import temporal_confidence, temporal_salience
    from pulse.core.uncertainty import wilson_interval

    item = {
        "id": "self-ref-id",
        "validity": "superseded",
        "title": "self-referential",
        "confidence": 0.6,
        "salience": 1.0,
        "type": "lesson",
    }

    mock_resolve = MagicMock(return_value="self-ref-id")  # same id = no chain

    with patch("pulse.brain.plasticity.reconsolidation._resolve_superseded_chain", mock_resolve):
        result = mod._score_item(
            item, "lessons", "test", "general",
            set(), None, {}, {},
            temporal_confidence, temporal_salience, wilson_interval,
        )

    assert result is not None
    assert "_resolved_to" not in result, (
        "_resolved_to should not be set when resolved ID == original ID"
    )


# ---------------------------------------------------------------------------
# Item 4: _render_items dead code removed from briefing
# ---------------------------------------------------------------------------

def test_render_items_not_defined_in_briefing():
    """_render_items must be removed from format_briefing to eliminate dead code."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert "def _render_items" not in src, (
        "_render_items dead code is still defined inside format_briefing — Item 4 fix missing"
    )


def test_render_items_not_called_anywhere_in_briefing():
    """_render_items must not be called anywhere in format_briefing."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert "_render_items(" not in src, (
        "_render_items is still called inside format_briefing"
    )


def test_briefing_module_has_no_render_items_at_module_level():
    """_render_items must not be a module-level attribute of briefing."""
    import pulse.brain.brain_query.briefing as mod
    assert not hasattr(mod, "_render_items"), (
        "_render_items was moved to module level — still accessible as dead code"
    )
