"""``/api/remittances`` and ``/api/remittances/stream`` — read views over the Remittance population.

Four endpoints, all gated by ``matrix_gate``:

- ``GET /api/remittances``                — paginated list with filter+sort,
  plus an NDJSON variant when the caller sends ``Accept: application/x-ndjson``.
- ``GET /api/remittances/summary``        — server-aggregated KPI tiles
  (``count``, ``total_paid``, ``total_adjustments``) over the full
  filtered population — never a page-limited sample (SP27 fix).
- ``GET /api/remittances/stream``         — NDJSON live-tail: snapshot
  of currently-known rows, then ``remittance_written`` events as
  they hit the store. Subscribed to by the Remittances page.
- ``GET /api/remittances/{remittance_id}`` — one remittance with its
  labeled CAS ``adjustments`` array. 404 on missing id (never 500).

``/api/remittances/stream`` is registered before
``/api/remittances/{remittance_id}`` so the literal ``stream`` path
segment is not captured as a remittance id.

SP36 Task 9: this block moved here from ``api.py:2448`` (the 4
``/api/remittances*`` routes, with the streaming route's
``NOTE: registered before…`` comment preserved verbatim).
"""
from __future__ import annotations

from typing import Any, AsyncIterator

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import StreamingResponse

from cyclone.api_helpers import (
    ndjson_line as _ndjson_line,
    ndjson_stream_list as _ndjson_stream_list,
    tail_events as _tail_events,
    wants_ndjson as _wants_ndjson,
)
from cyclone.auth.deps import matrix_gate
from cyclone.pubsub import EventBus
from cyclone.store import store

router = APIRouter(dependencies=[Depends(matrix_gate)])


@router.get("/api/remittances")
def list_remittances(
    request: Request,
    batch_id: str | None = Query(None),
    payer: str | None = Query(None),
    claim_id: str | None = Query(None),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    sort: str | None = Query(None),
    order: str = Query("desc"),
    limit: int = Query(100, ge=1, le=1000),
    offset: int = Query(0, ge=0),
) -> Any:
    common = dict(
        batch_id=batch_id,
        payer=payer,
        claim_id=claim_id,
        date_from=date_from,
        date_to=date_to,
    )
    items = list(store.iter_remittances(
        sort=sort, order=order, limit=limit, offset=offset, **common,
    ))
    # SP27 Task 13b: count the full population, not a 100-row sample.
    # See the matching note in list_claims — same silent-failure pattern.
    total = store.count_remittances(**common)
    returned = len(items)
    has_more = total > offset + returned
    if _wants_ndjson(request):
        return StreamingResponse(
            _ndjson_stream_list(items, total, returned, has_more),
            media_type="application/x-ndjson",
        )
    return {
        "items": items,
        "total": total,
        "returned": returned,
        "has_more": has_more,
    }


@router.get("/api/remittances/summary")
def remittances_summary(
    batch_id: str | None = Query(None),
    payer: str | None = Query(None),
    claim_id: str | None = Query(None),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
) -> dict:
    """Server-aggregated KPI tiles for the Remittances page.

    Returns ``{count, total_paid, total_adjustments}`` over the
    full filtered remittance population — NOT a page-limited
    sample. The Remittances page consumes this for its "Total paid"
    and "Adjustments" tiles so they can't silently understate the
    true DB population the way a page-local ``items.reduce(...)``
    would. Mirrors the silent-incompleteness fix that
    ``/api/dashboard/kpis`` (commit ``59c3275``) and
    ``/api/remittances`` (commit ``d81b6ed``) made for their tiles.

    Same filter parameters as ``/api/remittances``. Always returns
    a populated dict (``{"count": 0, "total_paid": 0,
    "total_adjustments": 0}`` when no rows match) so the frontend
    can render the tiles directly without a loading-vs-empty
    branch.
    """
    return store.summarize_remittances(
        batch_id=batch_id, payer=payer, claim_id=claim_id,
        date_from=date_from, date_to=date_to,
    )


@router.get("/api/remittances/stream")
async def remittances_stream(
    request: Request,
    payer: str | None = Query(None),
    claim_id: str | None = Query(None),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    sort: str | None = Query(None),
    order: str = Query("desc"),
    limit: int = Query(100, ge=1, le=1000),
) -> StreamingResponse:
    """Stream Remittances as NDJSON: snapshot first, then live events.

    Subscribes to ``remittance_written``. Default sort is
    ``-received_date`` (newest-first), matching the list endpoint's
    most common sort.

    NOTE: registered before ``/api/remittances/{remittance_id}`` so
    the literal ``stream`` path segment doesn't get matched as a
    remittance id.
    """
    bus: EventBus = request.app.state.event_bus

    async def gen() -> AsyncIterator[bytes]:
        rows = store.iter_remittances(
            payer=payer, claim_id=claim_id,
            date_from=date_from, date_to=date_to,
            sort=sort or "-received_date", order=order, limit=limit,
        )
        for row in rows:
            yield _ndjson_line({"type": "item", "data": row})
        yield _ndjson_line({"type": "snapshot_end", "data": {"count": len(rows)}})

        async for chunk in _tail_events(request, bus, ["remittance_written"]):
            yield chunk

    return StreamingResponse(gen(), media_type="application/x-ndjson")


@router.get("/api/remittances/{remittance_id}")
def get_remittance(remittance_id: str) -> dict:
    """Return one remittance with its labeled CAS ``adjustments`` array.

    Path param is ``remittance_id`` (not ``id``) to avoid shadowing
    FastAPI's internal ``id`` name and to keep OpenAPI docs self-
    describing. Returns 404 when the remittance is missing — never 500.
    """
    body = store.get_remittance(remittance_id)
    if body is None:
        raise HTTPException(
            status_code=404,
            detail={"error": "Not found", "detail": f"Remittance {remittance_id} not found"},
        )
    return body
