"""Secret-redaction helpers for log lines and error strings.

Notification clients embed secrets in URLs (Telegram bot tokens) and
Authorization headers (Matrix access tokens, ntfy bearer tokens). When
those secrets surface in ``aiohttp.ClientError.__str__``, response
bodies, or operator-visible error fields, they leak into logs and into
the per-target result dict that callers may forward upstream. ``redact``
returns a defanged copy safe for both contexts.
"""

from __future__ import annotations

import re
from typing import Final

# api.telegram.org/bot<digits>:<token>/<method>
_TELEGRAM_BOT_TOKEN_RE: Final = re.compile(
    r"(api\.telegram\.org/bot)\d+:[A-Za-z0-9_-]+", re.IGNORECASE,
)
# Authorization: Bearer <token>  (header form, case-insensitive)
_BEARER_RE: Final = re.compile(r"(Bearer\s+)[A-Za-z0-9._\-+/=]+", re.IGNORECASE)
# Discord webhook: /api/webhooks/<id>/<token>
_DISCORD_WEBHOOK_RE: Final = re.compile(
    r"(discord(?:app)?\.com/api/webhooks/\d+/)[A-Za-z0-9_-]+",
    re.IGNORECASE,
)
# Slack webhook path: /services/T.../B.../<token>
_SLACK_WEBHOOK_RE: Final = re.compile(
    r"(hooks\.slack\.com/services/[A-Z0-9]+/[A-Z0-9]+/)[A-Za-z0-9]+",
    re.IGNORECASE,
)
# URL userinfo: scheme://user:password@host
_URL_USERINFO_RE: Final = re.compile(
    r"([a-z][a-z0-9+\-.]*://)[^/@\s]+:[^/@\s]+@",
    re.IGNORECASE,
)
# Common token query parameters
_QUERY_TOKEN_RE: Final = re.compile(
    r"([?&](?:token|access_token|api_key|key|secret|password)=)[^&\s]+",
    re.IGNORECASE,
)


def redact(text: str) -> str:
    """Return ``text`` with known secret patterns replaced by ``***``.

    Idempotent and safe to call on already-redacted strings. Always
    returns a ``str``; non-strings are coerced via ``str()`` so callers
    can pass exception instances directly.
    """
    if not isinstance(text, str):
        text = str(text)
    text = _TELEGRAM_BOT_TOKEN_RE.sub(r"\1***", text)
    text = _DISCORD_WEBHOOK_RE.sub(r"\1***", text)
    text = _SLACK_WEBHOOK_RE.sub(r"\1***", text)
    text = _BEARER_RE.sub(r"\1***", text)
    text = _URL_USERINFO_RE.sub(r"\1***@", text)
    text = _QUERY_TOKEN_RE.sub(r"\1***", text)
    return text


def redact_exc(err: BaseException) -> str:
    """Redact-and-stringify an exception. Convenience for error fields."""
    return redact(str(err))
