"""``/api/ta1-acks`` — list, detail, and live-tail stream for TA1 envelopes.

TA1 is the interchange-control ACK (ISA/IEA acknowledgement). It's a
single segment, no functional group, no transaction set. Cyclone
persists the parsed fields plus a synthetic ``source_batch_id`` so the
row can sit alongside the 999 / 277CA ack rows without special-casing.

The detail endpoint also reconstructs the TA1 segment string
(``TA1*...~``) so the operator can copy it into a downstream tool.

SP25: ``/api/ta1-acks/stream`` joins the live-tail triplet — the
Acks page mounts ``useTailStream("ta1_acks")`` so the TA1 envelope
ack section sees new rows the moment they land.
"""
from __future__ import annotations

from typing import Any, AsyncIterator

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

from cyclone.api_helpers import ndjson_line, tail_events
from cyclone import db
from cyclone.pubsub import EventBus
from cyclone.store import store, to_ui_ta1_ack

router = APIRouter()


# SP25: ``_ta1_to_ui`` moved to ``cyclone.store.ui.to_ui_ta1_ack`` so
# the live-tail event payload (``ta1_ack_received``) matches the list
# endpoint shape byte-for-byte.


def _serialize_ta1_from_row(row: db.Ta1Ack) -> str:
    """Reconstruct a TA1 segment from the persisted flat row (for the detail endpoint)."""
    date_s = row.interchange_date.strftime("%y%m%d") if row.interchange_date else ""
    return (
        f"TA1*{row.control_number}*{date_s}*{row.interchange_time or ''}*"
        f"{row.ack_code}*{row.note_code or ''}~"
    )


@router.get("/api/ta1-acks")
def list_ta1_acks_endpoint(
    limit: int = Query(100, ge=1, le=1000),
) -> Any:
    """Return the list of persisted TA1 ACKs, newest first.

    Mirrors :func:`cyclone.api_routers.acks.list_acks_endpoint` — fetches all
    rows then slices in Python so the ``total`` field reflects the full row
    count regardless of the ``limit`` cap.
    """
    rows = store.list_ta1_acks()
    items = [to_ui_ta1_ack(r) for r in rows[:limit]]
    # SP28: batch-fetch linked_claim_ids per TA1 row (TA1 envelope
    # links always carry claim_id IS NULL — populate the field for
    # symmetry so the Acks page badge render path is uniform).
    ack_ids = [r.id for r in rows]
    linked_map = _find_linked_claim_ids_for_acks_helper(ack_ids, kind="ta1")
    for item, aid in zip(items, ack_ids[:limit]):
        item["linked_claim_ids"] = linked_map.get(aid, [])
    return {
        "total": len(rows),
        "items": items,
    }


def _find_linked_claim_ids_for_acks_helper(
    ack_ids: list[int], *, kind: str
) -> dict[int, list[str]]:
    """Batch-fetch {ack_id: [claim_id, …]} for the listed ack rows.

    Local helper so we don't import from ``acks.py`` and create a
    circular import. See the equivalent ``_find_linked_claim_ids_for_acks``
    in ``acks.py`` for the contract.
    """
    out: dict[int, list[str]] = {aid: [] for aid in ack_ids}
    if not ack_ids:
        return out
    with db.SessionLocal()() as s:
        rows = (
            s.query(db.ClaimAck.ack_id, db.ClaimAck.claim_id)
            .filter(
                db.ClaimAck.ack_kind == kind,
                db.ClaimAck.ack_id.in_(ack_ids),
                db.ClaimAck.claim_id.isnot(None),
            )
            .all()
        )
        for ack_id, claim_id in rows:
            out[ack_id].append(claim_id)
    return out


@router.get("/api/ta1-acks/stream")
async def ta1_acks_stream(
    request: Request,
    limit: int = Query(100, ge=1, le=1000),
) -> StreamingResponse:
    """Stream TA1 envelope acks as NDJSON.

    Subscribes to ``ta1_ack_received`` and emits the same wire format
    as the other live-tail endpoints. Registered BEFORE
    ``/api/ta1-acks/{ack_id}`` so ``stream`` isn't matched as an id.
    """
    bus: EventBus = request.app.state.event_bus

    async def gen() -> AsyncIterator[bytes]:
        rows = store.list_ta1_acks()[:limit]
        for row in rows:
            yield ndjson_line({"type": "item", "data": to_ui_ta1_ack(row)})
        yield ndjson_line({"type": "snapshot_end", "data": {"count": len(rows)}})
        async for chunk in tail_events(request, bus, ["ta1_ack_received"]):
            yield chunk

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


@router.get("/api/ta1-acks/{ack_id}")
def get_ta1_ack_endpoint(ack_id: int) -> dict:
    """Return one persisted TA1 ACK row with its parsed detail."""
    row = store.get_ta1_ack(ack_id)
    if row is None:
        raise HTTPException(status_code=404, detail=f"TA1 ACK {ack_id} not found")
    body = to_ui_ta1_ack(row)
    body["raw_ta1_text"] = _serialize_ta1_from_row(row)
    body["raw_json"] = row.raw_json
    return body
