"""f1api — the flagship-1 service made fully async. py-10's request-id middleware carries
over unchanged; what's new is a connection pool (`asyncpg.Pool` in production, `FakePool` in
tests, `f1api/pool.py`) that `/ask` goes through, a `/readyz` that reflects the pool's health
instead of a model's, and a shutdown that waits for every checked-out connection to come back
before closing the pool.
"""

from __future__ import annotations

import asyncio  # noqa: F401  — the drain loop's await asyncio.sleep(...)
import logging
import uuid  # noqa: F401  — mint the fallback id with uuid.uuid4()
from contextlib import AbstractAsyncContextManager
from typing import Protocol

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

from f1api.reqctx import current_request_id, request_scope  # noqa: F401
from f1api.schemas import AskRequest, AskResponse  # noqa: F401

log = logging.getLogger("f1api")


class ConnectionLike(Protocol):
    async def execute(self, query: str, *args: object) -> str: ...


class Pool(Protocol):
    """The slice of `asyncpg.Pool` (and `f1api.pool.FakePool`, its test double) that this
    module needs: an async-context-manager `acquire()`, `in_use` / `healthy` to read, `close()`
    to drain."""

    @property
    def in_use(self) -> int: ...
    @property
    def closed(self) -> bool: ...
    @property
    def healthy(self) -> bool: ...

    def acquire(self) -> AbstractAsyncContextManager[ConnectionLike]: ...
    async def close(self) -> None: ...


class RequestIdMiddleware:
    """Identical to py-10's — paste yours in, or rebuild it from the recipe below. One
    coroutine around the whole exchange, so the
    `with request_scope(rid):` below covers the route and every `await` inside it, including
    a DB round trip.

    `__init__(self, app)` stores `app`. `__call__(self, scope, receive, send)`:
      - if `scope["type"] != "http"`, just `await self.app(scope, receive, send)` and return;
      - read `x-request-id` out of `scope["headers"]` (lower-cased keys); use it if present,
        else `str(uuid.uuid4())`;
      - wrap `send` so that on `"http.response.start"` it sets
        `MutableHeaders(scope=message)["X-Request-Id"] = rid` before forwarding to `send`;
      - `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(pool: Pool) -> FastAPI:
    """TODO. Build the FastAPI app, `app.add_middleware(RequestIdMiddleware)`, and wire:

    - `POST /ask` — body validated as `AskRequest`. The answer is
      `f"hi there! you said: {body.prompt}"` — there is no real model here, the point of this
      exercise is the pool. Acquire a connection with `async with pool.acquire() as conn:` and
      `await conn.execute(...)` an insert logging `(current_request_id(), body.prompt, answer)`
      before returning `AskResponse(answer=answer, request_id=current_request_id())`.
    - `GET /healthz` — always `{"status": "ok"}`, always 200. Never touches `pool`.
    - `GET /readyz(response: Response)` — `{"ready": pool.healthy}`; set
      `response.status_code = 503` when `pool.healthy` is False.
    - a coroutine `drain_and_close()` — `while pool.in_use > 0: await asyncio.sleep(0.005)`,
      then `await pool.close()`. Register it two ways: `app.state.shutdown = drain_and_close`
      (so a test can call it directly, without depending on how a particular FastAPI version
      wires its shutdown event) *and* `@app.on_event("shutdown")` (so a real `uvicorn` shutdown
      runs it too). Skip the wait and the fake pool raises the instant a request is still
      mid-flight when shutdown runs; the real `asyncpg.Pool.close()` would just hang instead of
      telling you.

    Return the app.
    """
    ...
