"""PC-01: RequestIdMiddleware per-request overhead — baseline-relative (PF-01).

`RequestIdMiddleware.dispatch` extends Starlette's `BaseHTTPMiddleware`, whose
threaded-context wrapping has a documented ~20-50 µs overhead per request over
pure-ASGI middleware. On top of that, the dispatch adds:

  1. `request.headers.get(REQUEST_ID_HEADER.lower())` — header dict get.
  2. `_generate_request_id()` if absent — UUID4 allocation.
  3. `request.state.request_id = ...` — attr set.
  4. `_REQUEST_ID_VAR.set(...)` — contextvar set.
  5. `await call_next(request)` — the chain.
  6. `_REQUEST_ID_VAR.reset(token)` — contextvar reset.
  7. `response.headers[REQUEST_ID_HEADER] = ...` — header set.

Measurement strategy: build TWO identical FastAPI apps, one with the middleware
and one without; drive each via `httpx.AsyncClient` over an `ASGITransport` (so
no real network loop); take the delta as the net per-request overhead. That net
delta is recorded as a baseline (PF-03) and compared via
``baselines.assert_no_regression`` (PF-01/07) — NOT the old absolute 200 µs
budget.

Marker: ``perf`` (registered in pyproject.toml). Excluded from the default
suite; run via ``pytest -m perf``.
"""

from __future__ import annotations

import asyncio
import time
from typing import TYPE_CHECKING

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient

from mgf.fastapi import RequestIdMiddleware

if TYPE_CHECKING:
    from tests.perf.conftest import Baselines

pytestmark = pytest.mark.perf


def _build_app_with_middleware() -> FastAPI:
    app = FastAPI()
    app.add_middleware(RequestIdMiddleware)

    @app.get("/")
    async def root() -> dict[str, bool]:
        return {"ok": True}

    return app


def _build_app_without_middleware() -> FastAPI:
    app = FastAPI()

    @app.get("/")
    async def root() -> dict[str, bool]:
        return {"ok": True}

    return app


async def _measure_per_request_us(app: FastAPI, *, warm: int, n: int) -> float:
    """Drive `app` via httpx + ASGITransport; return µs/request."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://t") as client:
        for _ in range(warm):
            await client.get("/")

        start = time.perf_counter()
        for _ in range(n):
            await client.get("/")
        elapsed = time.perf_counter() - start

    return (elapsed / n) * 1_000_000


def test_net_overhead_no_regression(baselines: Baselines) -> None:
    async def _drive() -> tuple[float, float]:
        with_mw = await _measure_per_request_us(_build_app_with_middleware(), warm=200, n=2000)
        without_mw = await _measure_per_request_us(
            _build_app_without_middleware(), warm=200, n=2000
        )
        return with_mw, without_mw

    with_mw_us, without_mw_us = asyncio.run(_drive())
    net_overhead_us = with_mw_us - without_mw_us

    baselines.assert_no_regression(
        "request_id_net_overhead_p50_us",
        net_overhead_us,
        units="us",
        # Net delta is noisier than a single measurement (subtracts two
        # httpx-ASGI runs) — a wider threshold catches order-of-magnitude
        # regressions without false-positiving on subtraction variance.
        threshold_pct=200,
        description=(
            "RequestIdMiddleware net per-request overhead (with minus without), "
            "httpx-ASGI, 2000 reqs each"
        ),
    )
