"""``/api/acks`` — list & detail endpoints for the 999 ACK inbox.

These are the persisted acknowledgment rows produced by
``POST /api/parse-999``. The frontend ``useAcks`` hook re-shapes the
list payload to its ``Ack`` interface in ``src/types/index.ts``.

The detail endpoint returns the full ``raw_json`` payload plus the
regenerated ``raw_999_text`` so the UI can show "view source" without a
second round-trip.
"""
from __future__ import annotations

import logging
from typing import Any

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

from cyclone.api_helpers import ndjson_stream_list, wants_ndjson
from cyclone.parsers.models_999 import ParseResult999
from cyclone.parsers.serialize_999 import serialize_999
from cyclone.store import store

router = APIRouter()

log = logging.getLogger(__name__)


def _ack_to_ui(row) -> dict:
    """Map an ``Ack`` ORM row to the UI shape used by ``/api/acks``.

    Field names match the rest of the Cyclone API (snake_case). The
    frontend ``useAcks`` hook re-shapes this to the camelCase ``Ack``
    interface in ``src/types/index.ts``.
    """
    return {
        "id": row.id,
        "source_batch_id": row.source_batch_id,
        "accepted_count": row.accepted_count,
        "rejected_count": row.rejected_count,
        "received_count": row.received_count,
        "ack_code": row.ack_code,
        "parsed_at": (
            row.parsed_at.isoformat().replace("+00:00", "Z")
            if row.parsed_at is not None
            else ""
        ),
    }


@router.get("/api/acks")
def list_acks_endpoint(
    request: Request,
    limit: int = Query(100, ge=1, le=1000),
) -> Any:
    """Return the list of persisted 999 ACKs, newest first."""
    rows = store.list_acks()
    items = [_ack_to_ui(r) for r in rows[:limit]]
    total = len(rows)
    returned = len(items)
    has_more = total > 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/acks/{ack_id}")
def get_ack_endpoint(ack_id: int) -> dict:
    """Return one persisted ACK row with its parsed detail.

    Path param is ``ack_id`` (not ``id``) to avoid shadowing FastAPI's
    internal ``id`` name and to keep OpenAPI docs self-describing.
    Returns 404 when the ACK is missing — never 500.
    """
    row = store.get_ack(ack_id)
    if row is None:
        raise HTTPException(
            status_code=404,
            detail={"error": "Not found", "detail": f"Ack {ack_id} not found"},
        )
    body = _ack_to_ui(row)
    body["raw_json"] = row.raw_json
    # Regenerate the X12 text from raw_json so the operator can download
    # the actual 999 file. (SP3 P3 follow-up: list endpoint doesn't carry
    # the regenerated text to keep payloads small; detail does.)
    if row.raw_json:
        try:
            regenerated = ParseResult999.model_validate(row.raw_json)
            icn = regenerated.envelope.control_number or "000000001"
            body["raw_999_text"] = serialize_999(regenerated, interchange_control_number=icn)
        except Exception as exc:  # noqa: BLE001 — never 500 on a regen failure
            log.warning("Could not regenerate 999 for ack %s: %s", ack_id, exc)
            body["raw_999_text"] = None
    else:
        body["raw_999_text"] = None
    return body
