"""Credential and sensitive header redaction utilities.

Ensures that authentication values, API keys, bearer tokens, and other
sensitive headers are never included in logs, traces, serialized request
snapshots, or error messages.

Requirements: 2.8
"""
from __future__ import annotations

import re
from typing import Any, Mapping

from services.shared.inference.models import InferenceTarget

# ---------------------------------------------------------------------------
# Sensitive header detection
# ---------------------------------------------------------------------------

#: Header names (lowercase) that are always redacted.
SENSITIVE_HEADER_NAMES: frozenset[str] = frozenset(
    {
        "authorization",
        "x-api-key",
        "api-key",
        "x-auth-token",
        "proxy-authorization",
        "cookie",
        "set-cookie",
        "x-secret",
        "x-access-token",
    }
)

_REDACTED = "***REDACTED***"

# Patterns that look like bearer tokens or API keys in free text
_TOKEN_PATTERNS: list[re.Pattern[str]] = [
    # Bearer tokens
    re.compile(r"(Bearer\s+)\S+", re.IGNORECASE),
    # Common API key formats (sk-..., pk-..., key-..., token-..., api_key_...)
    re.compile(r"\b((?:sk|pk|key|token|api[_-]?key)[_-])\S{8,}", re.IGNORECASE),
]


# ---------------------------------------------------------------------------
# Header redaction
# ---------------------------------------------------------------------------


def redact_headers(headers: Mapping[str, str]) -> dict[str, str]:
    """Return a copy of headers with sensitive values replaced by a placeholder.

    Matches header names case-insensitively against ``SENSITIVE_HEADER_NAMES``.
    """
    result: dict[str, str] = {}
    for name, value in headers.items():
        if name.lower() in SENSITIVE_HEADER_NAMES:
            result[name] = _REDACTED
        else:
            result[name] = value
    return result


# ---------------------------------------------------------------------------
# Error message redaction
# ---------------------------------------------------------------------------


def redact_error_message(message: str) -> str:
    """Remove bearer tokens and API key patterns from text.

    Applied to error messages before they are logged or stored.
    """
    result = message
    for pattern in _TOKEN_PATTERNS:
        result = pattern.sub(
            lambda m: m.group(1) + _REDACTED if m.lastindex else _REDACTED,
            result,
        )
    return result


# ---------------------------------------------------------------------------
# Target redaction for logging / serialization
# ---------------------------------------------------------------------------


def redact_target_for_logging(target: InferenceTarget) -> dict[str, Any]:
    """Serialize an InferenceTarget for logging with credentials redacted.

    - ``auth_secret_ref`` is included as the reference name (e.g.
      "vault://inference/openai-key") so operators can identify which
      secret is in use. The target never holds the resolved secret value.
    - Sensitive headers in ``extra_headers`` are redacted.
    - ``extra_body`` is included as-is (callers should not place secrets there).

    Requirements: 2.8
    """
    return {
        "endpoint_id": str(target.endpoint_id),
        "deployment_id": str(target.deployment_id),
        "protocol": target.protocol,
        "base_url": target.base_url,
        "model": target.model,
        "capabilities": {
            "chat_completions": target.capabilities.chat_completions,
            "responses_api": target.capabilities.responses_api,
            "json_schema": target.capabilities.json_schema,
            "json_object": target.capabilities.json_object,
            "seed": target.capabilities.seed,
            "usage": target.capabilities.usage,
            "max_completion_tokens": target.capabilities.max_completion_tokens,
            "reasoning_toggle": target.capabilities.reasoning_toggle,
            "model_listing": target.capabilities.model_listing,
        },
        "auth_secret_ref": target.auth_secret_ref,
        "extra_headers": redact_headers(dict(target.extra_headers)),
        "extra_body": dict(target.extra_body),
    }
