"""`score` and `relevance` are different things — thresholds must use `relevance`.

Retrieval returns cosine similarities (~0.4-0.75) until the lexical leg returns
rows; then `_merge_sem_lex` replaces `score` with an RRF value (~0.008-0.02).
Both are legitimate *ranking* signals, but they are not on the same scale, so a
caller comparing `score >= 0.45` rejected every hit the moment BM25 matched
anything. That is how the citation-verification tab came to show no supporting
precedent for a single argument, on every case.

`relevance` is the fix: always a cosine, or None when the row came from the
lexical leg alone and no cosine was ever computed.
"""

import pytest

from legal_mcp.services.case_citation_verification import _SUGGEST_FLOOR, _passes_floor
from legal_mcp.services.hybrid_search import _merge_sem_lex


def _sem(key: str, score: float) -> dict:
    return {"chunk_id": key, "case_law_id": "c1", "score": score, "relevance": score}


def _lex(key: str, score: float) -> dict:
    # The lexical leg emits ts_rank_cd, never a cosine — so no `relevance`.
    return {"chunk_id": key, "case_law_id": "c1", "score": score}


def test_fusion_replaces_score_but_preserves_the_cosine():
    """The regression in one assertion."""
    merged = _merge_sem_lex([_sem("a", 0.73)], [_lex("a", 0.31)], limit=10)
    row = merged[0]
    assert row["score"] < 0.1, "fused score is an RRF value, not a cosine"
    assert row["relevance"] == pytest.approx(0.73), "the cosine must survive fusion"


def test_lexical_only_row_has_no_fabricated_cosine():
    """None means 'not measured' — not 'measured as irrelevant'."""
    merged = _merge_sem_lex([], [_lex("b", 0.31)], limit=10)
    assert merged[0]["relevance"] is None


def test_semantic_only_row_keeps_its_cosine():
    merged = _merge_sem_lex([_sem("c", 0.62)], [], limit=10)
    assert merged[0]["relevance"] == pytest.approx(0.62)


def test_floor_would_have_rejected_everything_on_the_fused_score():
    """Guards the exact production symptom: fused scores are ~0.008, the floor
    is 0.45, so score-based filtering wipes the result set."""
    merged = _merge_sem_lex(
        [_sem(k, 0.70) for k in "abcd"], [_lex(k, 0.30) for k in "abcd"], limit=10)
    assert all(r["score"] < _SUGGEST_FLOOR for r in merged)   # the bug
    assert all(_passes_floor(r) for r in merged)              # the fix


def test_floor_still_rejects_genuinely_weak_hits():
    """The fix must not become 'accept everything'."""
    assert not _passes_floor({"relevance": 0.10})
    assert not _passes_floor({"relevance": _SUGGEST_FLOOR - 0.01})
    assert _passes_floor({"relevance": _SUGGEST_FLOOR})


def test_lexical_only_hits_are_kept_deliberately():
    """An exact docket/phrase match reaches the top by BM25 rank with no cosine.
    Dropping it would hide precisely what a chair searches for by name."""
    assert _passes_floor({"relevance": None})
    assert _passes_floor({})  # missing key behaves the same as None
