"""Tests for ``GET /api/remittances/summary`` (server-aggregated KPI
backend for the Remittances page tiles).

Anomaly recap (see ``docs/superpowers/specs/2026-06-29-cyclone-...
-design.md`` if it lands later):

* The Remittances page's "Total paid" + "Adjustments" KPI tiles
  previously summed ``items.reduce(...)`` over the *current page* (25
  rows) plus whatever live-tail events had arrived in the session.
* For an empty-looking page with most-paid=0 rows, the UI rendered
  e.g. ``$16,934`` paid / ``$147`` adjustments — neither the page
  total ($1,334 / $143) nor the true DB total ($227,181 / $13,792).
  Same silent-incompleteness pattern the count bug retired in
  ``d81b6ed``: the page looked complete but the numbers were wrong.

The fix adds a server-aggregated summary endpoint — ``count + paid
+ adjustments`` summed over the full persisted row set with the same
filter pipeline as ``/api/remittances``. ``Remittances.tsx`` swaps
``items.reduce(...)`` for the new hook so the tiles reflect the true
DB population regardless of dataset size.

The covered cases match ``test_list_endpoint_counts.py`` /
``test_acks_aggregates.py`` for shape consistency.
"""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from decimal import Decimal

import pytest
from fastapi.testclient import TestClient

from cyclone import db
from cyclone.api import app
from cyclone.db import Batch, Remittance
from cyclone.store import store as global_store


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


def _seed_remit(
    s,
    remit_id: str,
    batch_id: str,
    *,
    received_at: datetime,
    total_paid: Decimal = Decimal("100.00"),
    adjustment_amount: Decimal = Decimal("0"),
    claim_id: str | None = None,
    payer_name: str = "Colorado Medicaid",
    status_code: str = "1",
) -> None:
    # The remittance's batch row carries ``raw_result_json`` with the
    # payer name because ``iter_remittances`` reads payer name from
    # there. We stage a fresh ``Batch`` row per call so unique
    # ``payer_name`` values for tests like the per-payer filter case
    # don't collide — the existing pattern from
    # ``test_list_endpoint_counts.py:88``.
    s.add(Batch(
        id=batch_id,
        kind="835",
        input_filename=f"{remit_id}.edi",
        parsed_at=received_at,
        totals_json={"total_claims": 1},
        validation_json={"passed": True, "warnings": [], "errors": []},
        raw_result_json={"payer": {"name": payer_name}},
    ))
    s.add(Remittance(
        id=remit_id,
        batch_id=batch_id,
        payer_claim_control_number=remit_id,
        claim_id=claim_id,
        status_code=status_code,
        total_charge=Decimal("100.00"),
        total_paid=total_paid,
        adjustment_amount=adjustment_amount,
        received_at=received_at,
    ))


# --------------------------------------------------------------------------- #
# Module-level: summarize_remittances
# --------------------------------------------------------------------------- #


def test_summarize_remittances_zero_when_empty():
    assert global_store.summarize_remittances() == {
        "count": 0,
        "total_paid": 0.0,
        "total_adjustments": 0.0,
    }


def test_summarize_remittances_unfiltered_returns_full_population():
    """Regression: with 5 remits seeded, the OLD ``items.reduce(...)``
    page-local sum was per-page (25 rows); the new helper must
    aggregate over all 5."""
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        # 5 remits, paid=10/20/30/40/50 → sum=150, adj all 0.
        for i, amt in enumerate([10, 20, 30, 40, 50]):
            _seed_remit(
                s, f"RMT-SUM-{i:04d}", f"b-sum-pop-{i}",
                received_at=base + timedelta(minutes=i),
                total_paid=Decimal(amt),
                adjustment_amount=Decimal("0"),
            )
        s.commit()

    assert global_store.summarize_remittances() == {
        "count": 5,
        "total_paid": 150.0,
        "total_adjustments": 0.0,
    }


def test_summarize_remittances_sums_adjustments_across_rows():
    """Adjustments accumulate independently from paid."""
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        _seed_remit(
            s, "RMT-A", "b-sum-adj-A",
            received_at=base, total_paid=Decimal("25.96"),
            adjustment_amount=Decimal("16.87"),
        )
        _seed_remit(
            s, "RMT-B", "b-sum-adj-B",
            received_at=base + timedelta(minutes=1),
            total_paid=Decimal("0"), adjustment_amount=Decimal("50.19"),
        )
        _seed_remit(
            s, "RMT-C", "b-sum-adj-C",
            received_at=base + timedelta(minutes=2),
            total_paid=Decimal("0"), adjustment_amount=Decimal("9.95"),
        )
        s.commit()

    assert global_store.summarize_remittances() == {
        "count": 3,
        "total_paid": 25.96,
        "total_adjustments": 77.01,  # 16.87 + 50.19 + 9.95
    }


def test_summarize_remittances_filters_by_claim_id():
    """claim_id filter narrows the summary the same way it narrows
    iter_remittances (DB-side WHERE remittance.claim_id = :claim_id).
    """
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        _seed_remit(
            s, "RMT-X", "b-sum-cid-X",
            received_at=base, claim_id="CLM-1",
            total_paid=Decimal("10"), adjustment_amount=Decimal("0"),
        )
        _seed_remit(
            s, "RMT-Y", "b-sum-cid-Y",
            received_at=base, claim_id="CLM-2",
            total_paid=Decimal("20"), adjustment_amount=Decimal("1"),
        )
        _seed_remit(
            s, "RMT-Z", "b-sum-cid-Z",
            received_at=base, claim_id=None,
            total_paid=Decimal("30"), adjustment_amount=Decimal("2"),
        )
        s.commit()

    assert global_store.summarize_remittances(claim_id="CLM-1") == {
        "count": 1,
        "total_paid": 10.0,
        "total_adjustments": 0.0,
    }
    assert global_store.summarize_remittances(claim_id="CLM-2") == {
        "count": 1,
        "total_paid": 20.0,
        "total_adjustments": 1.0,
    }
    assert global_store.summarize_remittances(claim_id="CLM-missing") == {
        "count": 0,
        "total_paid": 0.0,
        "total_adjustments": 0.0,
    }


def test_summarize_remittances_filters_by_payer_exact_match():
    """``payer`` filter is in-memory + case-sensitive exact match —
    the helper must apply it the same way ``iter_remittances`` does
    so summary and list views agree under the same chip."""
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        _seed_remit(
            s, "RMT-CO-1", "b-sum-payer-co-1",
            received_at=base, payer_name="Colorado Medicaid",
            total_paid=Decimal("7"), adjustment_amount=Decimal("0.50"),
        )
        _seed_remit(
            s, "RMT-CO-2", "b-sum-payer-co-2",
            received_at=base + timedelta(minutes=1),
            payer_name="Colorado Medicaid",
            total_paid=Decimal("13"), adjustment_amount=Decimal("0.25"),
        )
        _seed_remit(
            s, "RMT-AZ-1", "b-sum-payer-az-1",
            received_at=base + timedelta(minutes=2),
            payer_name="Arizona Medicaid",
            total_paid=Decimal("99"), adjustment_amount=Decimal("0"),
        )
        s.commit()

    assert global_store.summarize_remittances(payer="Colorado Medicaid") == {
        "count": 2,
        "total_paid": 20.0,
        "total_adjustments": 0.75,
    }
    assert global_store.summarize_remittances(payer="Arizona Medicaid") == {
        "count": 1,
        "total_paid": 99.0,
        "total_adjustments": 0.0,
    }
    assert global_store.summarize_remittances(payer="colorado medicaid") == {
        "count": 0,
        "total_paid": 0.0,
        "total_adjustments": 0.0,
    }


# --------------------------------------------------------------------------- #
# HTTP: /api/remittances/summary
# --------------------------------------------------------------------------- #


def test_api_remittances_summary_empty(client: TestClient):
    """Empty DB → zero totals, well-formed response."""
    resp = client.get("/api/remittances/summary")
    assert resp.status_code == 200, resp.text
    assert resp.json() == {
        "count": 0,
        "total_paid": 0,
        "total_adjustments": 0,
    }


def test_api_remittances_summary_full_population(client: TestClient):
    """Bug repro: seed 5 remits with known amounts, assert the
    endpoint reports the FULL sum, not a 25-row sample.
    """
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        for i, amt in enumerate([100, 200, 300, 400, 500]):
            _seed_remit(
                s, f"RMT-HTTP-SUM-{i}", f"b-http-sum-{i}",
                received_at=base + timedelta(minutes=i),
                total_paid=Decimal(amt),
                adjustment_amount=Decimal("5"),
            )
        s.commit()

    resp = client.get("/api/remittances/summary")
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert body == {
        "count": 5,
        "total_paid": 1500,        # 100+200+300+400+500
        "total_adjustments": 25,   # 5*5
    }


def test_api_remittances_summary_respects_claim_id_filter(client: TestClient):
    """Filter narrows both count and sums."""
    base = datetime(2026, 6, 1, 12, 0, tzinfo=timezone.utc)
    with db.SessionLocal()() as s:
        _seed_remit(
            s, "RMT-F-1", "b-http-cid-1",
            received_at=base, claim_id="CLM-F1",
            total_paid=Decimal("11"), adjustment_amount=Decimal("1"),
        )
        _seed_remit(
            s, "RMT-F-2", "b-http-cid-2",
            received_at=base, claim_id="CLM-F2",
            total_paid=Decimal("22"), adjustment_amount=Decimal("2"),
        )
        s.commit()

    resp = client.get(
        "/api/remittances/summary",
        params={"claim_id": "CLM-F1"},
    )
    assert resp.status_code == 200
    assert resp.json() == {
        "count": 1,
        "total_paid": 11,
        "total_adjustments": 1,
    }
