"""Tests for GET /api/payers/{payer_id}/summary (SP21 Task 1.5).

The endpoint is the payer-level aggregate that the drill-down UI's
"Payer → Claims" panel hangs off. It returns billed/received totals,
denial rate, and the top 5 NPIs by claim volume for one payer_id,
cached in-process for 60s.

The minimal 837P fixture ships one CLM with ``payer_id="SKCO0"``,
charge_amount=100.00; the minimal 835 carries one CLP for the same
claim with total_paid=85.00. So ``/api/payers/SKCO0/summary`` returns
``claim_count >= 1`` after both files are ingested.

Note: the spec calls this ``payer_id`` (the X12 NM1*PR*PI qualifier,
e.g. ``SKCO0``). It is NOT the configured payer name from
``config/payers.yaml``. The filter key in the store layer is
``Claim.payer_id`` — not the ``payer=`` substring filter used by
``/api/claims?payer=...``.
"""
from __future__ import annotations

from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from cyclone import api as api_mod
from cyclone.api import app

FIXTURE_837 = Path(__file__).parent / "fixtures" / "minimal_837p.txt"
FIXTURE_835 = Path(__file__).parent / "fixtures" / "minimal_835.txt"


@pytest.fixture(autouse=True)
def _clear_summary_cache():
    """Wipe the in-process payer-summary cache between tests.

    conftest resets the DB per test but the cache is module-level
    state on ``cyclone.api``. Without this clear, a stale payload
    from a previous test's seed would leak into a later test's first
    call — masking recompute behavior. The 60s TTL is the only
    invalidation story today (see api.py docstring on the endpoint).
    """
    api_mod._clear_summary_cache()
    yield
    api_mod._clear_summary_cache()


@pytest.fixture
def client() -> TestClient:
    return TestClient(app)


@pytest.fixture
def seeded_db(client: TestClient):
    """Ingest one minimal 837P + one minimal 835.

    Both fixtures carry ``payer_id="SKCO0"`` so the summary endpoint
    has something to aggregate. ``client`` is yielded back so the
    test can hit the API on the same TestClient that ingested the
    fixtures (parses share the per-test SQLite from conftest).
    """
    text_837 = FIXTURE_837.read_text()
    text_835 = FIXTURE_835.read_text()
    r837 = client.post(
        "/api/parse-837",
        files={"file": ("x.txt", text_837, "text/plain")},
        headers={"Accept": "application/json"},
    )
    assert r837.status_code == 200, r837.text
    r835 = client.post(
        "/api/parse-835",
        files={"file": ("era.txt", text_835, "text/plain")},
        headers={"Accept": "application/json"},
    )
    assert r835.status_code == 200, r835.text
    return client


def test_payer_summary_happy_path(seeded_db: TestClient):
    """Seeded db has at least one claim for SKCO0 → 200 with the spec shape."""
    resp = seeded_db.get("/api/payers/SKCO0/summary")
    assert resp.status_code == 200, resp.text
    data = resp.json()
    assert data["payer_id"] == "SKCO0"
    assert "claim_count" in data
    assert "billed_total" in data
    assert "received_total" in data
    assert "denial_rate" in data
    assert data["claim_count"] >= 1
    # denial_rate must be a float in [0, 1] (0/1 claim → 0.0).
    assert isinstance(data["denial_rate"], (int, float))
    assert 0.0 <= data["denial_rate"] <= 1.0


def test_payer_summary_unknown_payer_returns_404(client: TestClient):
    resp = client.get("/api/payers/DOES_NOT_EXIST/summary")
    assert resp.status_code == 404


def test_payer_summary_caches_then_invalidates(seeded_db: TestClient):
    """Two back-to-back calls return identical payloads (in-process cache)."""
    resp1 = seeded_db.get("/api/payers/SKCO0/summary")
    resp2 = seeded_db.get("/api/payers/SKCO0/summary")
    assert resp1.status_code == 200
    assert resp2.status_code == 200
    assert resp1.json() == resp2.json()