"""The citation view is cached by input fingerprint, not by a guessed TTL.

Assembling it costs 20+ seconds of Voyage embeds and vector searches. A TTL
would force a choice between staleness and cost: a chair who verifies a
precedent must see it on the very next load, and a timer cannot promise that.
Keying on a fingerprint of the inputs (arguments · attachments · corpus) makes
freshness a property of the data rather than of the clock.
"""

import asyncio

import pytest

from legal_mcp.services import case_citation_verification as ccv


@pytest.fixture(autouse=True)
def _clean_cache():
    ccv.invalidate()
    yield
    ccv.invalidate()


def _stub(monkeypatch, fingerprint: str, calls: list, *, complete: bool = True):
    """Point build_view at a fake case, a controllable fingerprint, and a
    counted assembler, so these tests never touch the corpus."""
    async def fake_case(_cn):
        return {"id": "00000000-0000-0000-0000-000000000001"}

    async def fake_fp(_cid):
        return fingerprint

    async def fake_build(cn):
        calls.append(cn)
        return {"status": "ok", "case_number": cn, "arguments": [],
                "retrieval_complete": complete, "summary": {}}

    monkeypatch.setattr(ccv.db, "get_case_by_number", fake_case)
    monkeypatch.setattr(ccv, "_inputs_fingerprint", fake_fp)
    monkeypatch.setattr(ccv, "_build_view_uncached", fake_build)


@pytest.mark.asyncio
async def test_second_load_is_served_from_cache(monkeypatch):
    calls: list = []
    _stub(monkeypatch, "fp-1", calls)
    first = await ccv.build_view("1069-04-26")
    second = await ccv.build_view("1069-04-26")
    assert first["cached"] is False
    assert second["cached"] is True
    assert len(calls) == 1, "the expensive assembly must run once"


@pytest.mark.asyncio
async def test_changed_inputs_invalidate_without_anyone_calling_invalidate(monkeypatch):
    """The chair verifies a precedent → fingerprint moves → next load rebuilds.
    Correctness must not depend on a writer remembering to clear the cache."""
    calls: list = []
    _stub(monkeypatch, "fp-before", calls)
    await ccv.build_view("1069-04-26")

    _stub(monkeypatch, "fp-after", calls)  # e.g. a new case_precedents row
    again = await ccv.build_view("1069-04-26")
    assert again["cached"] is False
    assert len(calls) == 2


@pytest.mark.asyncio
async def test_concurrent_loads_do_not_stampede(monkeypatch):
    """Reload-during-a-slow-load is how the original outage compounded into
    Postgres deadlocks. The later readers must wait, not pile on."""
    calls: list = []

    async def fake_case(_cn):
        return {"id": "00000000-0000-0000-0000-000000000001"}

    async def fake_fp(_cid):
        return "fp-1"

    async def slow_build(cn):
        calls.append(cn)
        await asyncio.sleep(0.05)
        return {"status": "ok", "case_number": cn, "arguments": [],
                "retrieval_complete": True, "summary": {}}

    monkeypatch.setattr(ccv.db, "get_case_by_number", fake_case)
    monkeypatch.setattr(ccv, "_inputs_fingerprint", fake_fp)
    monkeypatch.setattr(ccv, "_build_view_uncached", slow_build)

    await asyncio.gather(*(ccv.build_view("1069-04-26") for _ in range(6)))
    assert len(calls) == 1, "six concurrent loads, one assembly"


@pytest.mark.asyncio
async def test_partial_view_expires_so_missing_suggestions_get_retried(monkeypatch):
    """A budget-exhausted view is cached to stop reload storms, but it must not
    become permanent — the arguments with no suggestions deserve another try."""
    calls: list = []
    _stub(monkeypatch, "fp-1", calls, complete=False)
    await ccv.build_view("1069-04-26")
    _fp, expires_at, _view = ccv._view_cache["1069-04-26"]
    assert expires_at is not None, "a partial view must carry an expiry"

    monkeypatch.setattr(ccv.time, "monotonic", lambda: expires_at + 1)
    after = await ccv.build_view("1069-04-26")
    assert after["cached"] is False
    assert len(calls) == 2


@pytest.mark.asyncio
async def test_complete_view_does_not_expire_on_a_timer(monkeypatch):
    calls: list = []
    _stub(monkeypatch, "fp-1", calls, complete=True)
    await ccv.build_view("1069-04-26")
    _fp, expires_at, _view = ccv._view_cache["1069-04-26"]
    assert expires_at is None, "freshness comes from the fingerprint, not a clock"


@pytest.mark.asyncio
async def test_use_cache_false_always_rebuilds(monkeypatch):
    calls: list = []
    _stub(monkeypatch, "fp-1", calls)
    await ccv.build_view("1069-04-26")
    await ccv.build_view("1069-04-26", use_cache=False)
    assert len(calls) == 2
