"""接口指标埋点:有界事件队列 + 纯 ASGI 中间件。

每个 HTTP 请求测总耗时、抓路由模板 + 状态码,非阻塞塞进有界队列;由 observe_worker
后台批量上报到 OpenObserve。请求路径上无任何 I/O。未配置观测时中间件直接透传。
"""
from __future__ import annotations

import asyncio
import os
import time

from starlette.routing import Match

from app.core.config import settings

# 不采集的路径(纯噪音):健康检查。
_SKIP_PATHS = frozenset({"/health"})
# 未匹配路由(404/扫描器)归一到此,防维度爆炸。
_UNMATCHED = "__unmatched__"
# service 字段:与 logging.py 同源(LOG_SERVICE_NAME),默认 app-server。
_SERVICE = os.getenv("LOG_SERVICE_NAME", "app-server")

# 有界事件队列(懒创建,见 get_queue):首次取用时在运行中的 loop 里建,避免 import 期
# 无 loop 的边角问题;put_nowait/get_nowait 不需运行中的 loop → 可在无 loop 下测试。
_queue: asyncio.Queue[dict] | None = None
# 队列满时的丢弃计数,worker 定期取出打日志。
_dropped = 0


def get_queue() -> asyncio.Queue[dict]:
    """返回全局有界事件队列(懒创建)。测试可 monkeypatch 模块级 _queue 换成小队列。"""
    global _queue
    if _queue is None:
        _queue = asyncio.Queue(maxsize=settings.OBSERVE_QUEUE_MAX)
    return _queue


def take_dropped() -> int:
    """取出并清零累计丢弃数(供 worker 打点)。"""
    global _dropped
    n, _dropped = _dropped, 0
    return n


def record_event(event: dict) -> None:
    """非阻塞入队;队列满则丢弃当前事件并计数。永不抛异常、永不阻塞请求。"""
    global _dropped
    try:
        get_queue().put_nowait(event)
    except asyncio.QueueFull:
        _dropped += 1


def _resolve_route(scope) -> str:
    """从 scope 取路由模板(如 /things/{tid})。优先 scope['route'](现代 Starlette
    路由后写入);取不到则手动匹配一次(老版本兜底);仍无 → __unmatched__(404/扫描器)。"""
    route = scope.get("route")
    path = getattr(route, "path", None)
    if path:
        return path
    app_ = scope.get("app")
    router = getattr(app_, "router", None)
    for candidate in getattr(router, "routes", []):
        try:
            match, _ = candidate.matches(scope)
        except Exception:  # noqa: BLE001 - 匹配兜底,任一路由异常不影响整体
            continue
        if match == Match.FULL and getattr(candidate, "path", None):
            return candidate.path
    return _UNMATCHED


class RequestMetricsMiddleware:
    """纯 ASGI 中间件:测每个 http 请求耗时,记 method/route/status/duration。

    放在最外层(main.py 里 CORS 之后 add),测到含 CORS 的完整耗时。未配置观测 → 透传。
    """

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

    async def __call__(self, scope, receive, send) -> None:
        if scope["type"] != "http" or not settings.observe_configured:
            await self.app(scope, receive, send)
            return
        if scope.get("path") in _SKIP_PATHS:
            await self.app(scope, receive, send)
            return

        start = time.perf_counter()
        status_holder = {"status": 500}  # 下游异常未产出 response 时兜底 500

        async def send_wrapper(message) -> None:
            if message["type"] == "http.response.start":
                status_holder["status"] = message["status"]
            await send(message)

        try:
            await self.app(scope, receive, send_wrapper)
        finally:
            duration_ms = (time.perf_counter() - start) * 1000.0
            record_event({
                "_timestamp": int(time.time() * 1_000_000),  # µs,OpenObserve 时间列
                "service": _SERVICE,
                "env": settings.APP_ENV,
                "method": scope.get("method", ""),
                "route": _resolve_route(scope),
                "status": status_holder["status"],
                "duration_ms": round(duration_ms, 3),
            })
