"""request_id 关联中间件（ARCH §9.3 / CLAUDE.md）。

每请求生成/透传 request_id，绑入 structlog contextvars，并回写响应头，
错误信封也带上同一 id，便于端到端 grep。
"""

from __future__ import annotations

import re
import uuid
from collections.abc import Awaitable, Callable

import structlog
from starlette.requests import Request
from starlette.responses import Response

REQUEST_ID_HEADER = "x-request-id"

# 放行安全字符集（字母数字 . _ -，1–128 位）：覆盖 uuid4().hex、常见 trace id 与客户端
# 自定义短 id；含空白/控制字符/换行/注入序列/超长的一律丢弃改生成新 uuid——防止把未经
# 校验的客户端值原样写进日志/响应头（日志注入防护）。
_REQUEST_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,128}$")


def _sanitize_request_id(raw: str | None) -> str:
    if raw is not None and _REQUEST_ID_PATTERN.match(raw):
        return raw
    return uuid.uuid4().hex


async def request_id_middleware(
    request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
    request_id = _sanitize_request_id(request.headers.get(REQUEST_ID_HEADER))
    structlog.contextvars.clear_contextvars()
    structlog.contextvars.bind_contextvars(request_id=request_id)
    request.state.request_id = request_id
    response = await call_next(request)
    response.headers[REQUEST_ID_HEADER] = request_id
    return response
