"""The FastAPI app: three doors. /ask and /stream reach the model; /healthz never does;
/readyz reads a flag. Every request gets an X-Request-Id, incoming or freshly minted, kept in
`reqctx` for the request's whole lifetime — including a StreamingResponse's body as it sends.
"""

from __future__ import annotations

import logging
import uuid  # noqa: F401  — mint the fallback id with uuid.uuid4()
from collections.abc import AsyncIterator  # noqa: F401  — the /stream generator's return type

from fastapi import FastAPI, Response  # noqa: F401  — Response is /readyz's status-code hook
from fastapi.responses import StreamingResponse  # noqa: F401
from starlette.datastructures import MutableHeaders  # noqa: F401
from starlette.types import ASGIApp, Receive, Scope, Send

from askapi.model import Model
from askapi.reqctx import current_request_id, request_scope  # noqa: F401
from askapi.schemas import AskRequest, AskResponse  # noqa: F401

log = logging.getLogger("askapi")


class RequestIdMiddleware:
    """TODO: a raw ASGI middleware — the form Starlette's docs recommend over
    `@app.middleware("http")` (`BaseHTTPMiddleware`). It is one coroutine around the whole
    exchange: `with request_scope(rid): await self.app(...)` covers the route, the response
    start and every streamed body chunk, and the header goes on by editing the
    `http.response.start` message as it passes. (The decorator would carry the id here too —
    it copies the ContextVar into the task that runs your route — but it re-wraps every
    response body in a stream of its own and never sees a ContextVar set downstream.)

    `__init__(self, app)` stores `app`. `__call__(self, scope, receive, send)`:
      - if `scope["type"] != "http"`, just `await self.app(scope, receive, send)` and return
        (websockets/lifespan have no headers to read here);
      - read `x-request-id` out of `scope["headers"]` (a list of `(bytes, bytes)` pairs — keys
        arrive lower-cased); use it if present, else `str(uuid.uuid4())`;
      - wrap `send` so that on the `"http.response.start"` message it sets
        `MutableHeaders(scope=message)["X-Request-Id"] = rid` before forwarding to the real
        `send` — that's how a response header gets added without building the response object
        yourself;
      - `with request_scope(rid): await self.app(scope, receive, wrapped_send)`.
    """

    def __init__(self, app: ASGIApp) -> None:
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        ...


def create_app(model: Model) -> FastAPI:
    """TODO. Build the FastAPI app, `app.add_middleware(RequestIdMiddleware)`, and wire:

    - `POST /ask` — body validated as `AskRequest`; log one line ("ask served") on `log`,
      with `extra={"request_id": current_request_id()}`, *before* returning; return
      `AskResponse(answer=model.answer(body.prompt), request_id=current_request_id())`;
    - `GET /stream` — a `prompt: str = ""` query param; `StreamingResponse` over an async
      generator that, for each chunk `model.stream(prompt)` yields, sends
      `f"event: token\\ndata: {chunk}\\n\\n"`, then a final `"event: done\\ndata: {}\\n\\n"`
      and one "stream served" log line (same `extra=` shape as /ask);
      `media_type="text/event-stream"`;
    - `GET /healthz` — always `{"status": "ok"}`, always 200. Never touches `model`;
    - `GET /readyz(response: Response)` — `{"ready": model.loaded}`; set
      `response.status_code = 503` when `model.loaded` is False, else leave it at 200.

    Return the app.
    """
    ...
