import uuid
from datetime import datetime, timezone
from fastapi import Request

SENSITIVE_HEADERS = {"authorization", "cookie", "x-api-key", "proxy-authorization"}
MAX_BODY_LOG_BYTES = 4096  # don't log huge payloads


def _safe(fn, default=None):
    """Run fn(), swallow any exception, return default instead."""
    try:
        return fn()
    except Exception:
        return default


def _json_safe(value):
    """Best-effort convert anything to something JSON-serializable."""
    try:
        import json
        json.dumps(value)
        return value
    except Exception:
        return str(value)


def _redact_headers(headers: dict) -> dict:
    redacted = {}
    for k, v in headers.items():
        if k.lower() in SENSITIVE_HEADERS:
            redacted[k] = "***REDACTED***"
        else:
            redacted[k] = v
    return redacted


async def capture_request_metadata(request: Request, read_body: bool = False) -> dict:
    """
    Capture as much forensic metadata about an inbound request as possible,
    without ever raising — designed for logging/monitoring a public API.
    """

    headers = _safe(lambda: dict(request.headers), {})
    query_params = _safe(lambda: dict(request.query_params), {})
    path_params = _safe(lambda: dict(request.path_params), {})
    cookies = _safe(lambda: dict(request.cookies), {})

    metadata = {
        "request_id": str(uuid.uuid4()),
        "timestamp": datetime.now(timezone.utc).isoformat(),

        # Client
        "client": {
            "host": _safe(lambda: request.client.host if request.client else None),
            "port": _safe(lambda: request.client.port if request.client else None),
        },

        # Request line
        "method": _safe(lambda: request.method),
        "url": _safe(lambda: str(request.url)),
        "base_url": _safe(lambda: str(request.base_url)),
        "path": _safe(lambda: request.url.path),
        "query_string": _safe(lambda: request.url.query),
        "scheme": _safe(lambda: request.url.scheme),

        # ASGI / HTTP-level
        "http_version": _safe(lambda: request.scope.get("http_version")),
        "server": _safe(lambda: request.scope.get("server")),
        "root_path": _safe(lambda: request.scope.get("root_path")),

        "path_params": path_params,
        "query_params": query_params,
        "cookies": _redact_headers(cookies),

        # Headers — full copy, but with secrets redacted
        "headers": _redact_headers(headers),

        # Commonly useful individual fields (fast to query without digging into headers dict)
        "user_agent": headers.get("user-agent"),
        "referer": headers.get("referer"),
        "origin": headers.get("origin"),
        "accept_language": headers.get("accept-language"),
        "accept_encoding": headers.get("accept-encoding"),
        "content_type": headers.get("content-type"),
        "content_length": headers.get("content-length"),

        # Proxy / real-IP related
        "forwarded": {
            "x_forwarded_for": headers.get("x-forwarded-for"),
            "x_real_ip": headers.get("x-real-ip"),
            "forwarded": headers.get("forwarded"),
            "cf_connecting_ip": headers.get("cf-connecting-ip"),
            "cf_ipcountry": headers.get("cf-ipcountry"),
            "cf_ray": headers.get("cf-ray"),
            "true_client_ip": headers.get("true-client-ip"),
        },

        # Raw-ish scope, filtered to JSON-safe fields only
        "scope": {
            "type": _safe(lambda: request.scope.get("type")),
            "scheme": _safe(lambda: request.scope.get("scheme")),
            "http_version": _safe(lambda: request.scope.get("http_version")),
            "server": _safe(lambda: request.scope.get("server")),
            "client": _safe(lambda: request.scope.get("client")),
            "root_path": _safe(lambda: request.scope.get("root_path")),
            # NOTE: intentionally NOT including scope["extensions"] — often
            # contains non-serializable objects (TLS info etc.)
        },
    }

    # Optional: capture body (for a chatbot, this is usually the message payload)
    if read_body:
        body_bytes = await _safe_read_body(request)
        if body_bytes is not None:
            metadata["body_size_bytes"] = len(body_bytes)
            truncated = body_bytes[:MAX_BODY_LOG_BYTES]
            metadata["body_preview"] = _safe(
                lambda: truncated.decode("utf-8", errors="replace")
            )
            metadata["body_truncated"] = len(body_bytes) > MAX_BODY_LOG_BYTES

    # Final safety net: make 100% sure everything is JSON-serializable
    return _json_safe_dict(metadata)


async def _safe_read_body(request: Request):
    try:
        return await request.body()
    except Exception:
        return None


def _json_safe_dict(d):
    if isinstance(d, dict):
        return {k: _json_safe_dict(v) for k, v in d.items()}
    if isinstance(d, list):
        return [_json_safe_dict(v) for v in d]
    return _json_safe(d)