"""Shared request-id contextvar + helpers.

Federation-wide request-id propagation. Both ``mgf.django`` and
``mgf.fastapi`` middleware adapters share the same contextvar so
logging filters that pull ``current_request_id()`` work end-to-end
across mixed-framework processes (e.g., a Django app calling a
FastAPI service via an internal RPC).

The public surface lives at the ``mgf.common`` top level — see
``mgf.common.__init__`` for the re-exports. Consumers SHOULD NOT
import from this private module directly; the only legitimate
caller is the federation's framework-adapter siblings, and even
they go through the public re-exports as of PAPER-61
(2026-05-25).

Public API surface:

  - ``REQUEST_ID_HEADER`` — canonical wire header name.
  - ``current_request_id()`` — read the current id (empty string
    outside a request).
  - ``generate_request_id()`` — fresh UUID4 id; tests can
    monkeypatch.
  - ``set_request_id(rid)`` / ``reset_request_id(token)`` — paired
    write API for middleware-shape callers. Wraps the underlying
    ``ContextVar.set()`` / ``.reset()`` discipline so callers
    never see the raw ContextVar.
"""

from __future__ import annotations

import contextvars
import uuid

REQUEST_ID_HEADER = "X-Request-Id"
"""Canonical header name. Many providers ship as ``X-Request-Id``;
some use ``Request-Id`` or ``X-Correlation-Id``. We accept any
case variant on input but always echo as ``X-Request-Id``."""


# Per-request contextvar. Default empty string so reads from outside
# a request return a falsy value rather than raising.
#
# Private. Federation framework-adapter siblings MUST NOT touch
# this directly — go through `set_request_id` / `reset_request_id`
# below. PAPER-61 codified the public surface; the raw ContextVar
# stays private to prevent middleware authors from bypassing the
# get/set discipline.
_REQUEST_ID_VAR: contextvars.ContextVar[str] = contextvars.ContextVar(
    "mgf_request_id",
    default="",
)


def current_request_id() -> str:
    """Return the current request's ID, or ``""`` outside a request.

    Reads a per-task contextvar set by the framework adapter's
    request-id middleware. Safe to call from any code path inside an
    HTTP request handler, including async children. Outside a
    request, returns the empty string — code SHOULD treat that as
    "no request id available."
    """
    return _REQUEST_ID_VAR.get()


def generate_request_id() -> str:
    """Generate a fresh UUID4 string for a request id.

    Separate function so tests can monkeypatch deterministic ids.
    Public since PAPER-61 (was ``_generate_request_id`` before
    2026-05-25 — renamed when promoted from private to public
    surface).
    """
    return str(uuid.uuid4())


def set_request_id(request_id: str) -> contextvars.Token[str]:
    """Set the current request id; return a Token to undo with
    :func:`reset_request_id`.

    Use in middleware::

        token = set_request_id(rid)
        try:
            ...
        finally:
            reset_request_id(token)

    Public since PAPER-61. Wraps ``ContextVar.set`` so callers
    don't need access to the underlying ContextVar.
    """
    return _REQUEST_ID_VAR.set(request_id)


def reset_request_id(token: contextvars.Token[str]) -> None:
    """Restore the request id to what it was before the matching
    :func:`set_request_id` call.

    Pair with the Token returned by ``set_request_id``. Public
    since PAPER-61.
    """
    _REQUEST_ID_VAR.reset(token)
