import re
from typing import Any

REDACTED = "[REDACTED]"

class PIIRedactor:
    """Best-effort PII/sensitive-data redaction for logs."""

    SENSITIVE_KEYS = {
        "password", "password_hash", "token", "access_token",
        "refresh_token", "authorization", "auth", "secret",
        "client_secret", "api_key", "x_api_key", "iban",
        "account_number", "card_number", "cvv",
    }

    IBAN_RE = re.compile(r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b")
    CARD_RE = re.compile(r"\b(?:\d[ -]*?){13,19}\b")

    TOKEN_PATTERNS = [
        re.compile(r"(?i)\b(authorization)\s*[:=]\s*bearer\s+[a-z0-9\-._~+/]+=*"),
        re.compile(r"(?i)\b(access_token|refresh_token|token|api_key|secret)\b\s*[:=]\s*['\"]?[^,'\"\\s]+"),
        re.compile(r"(?i)\b(password|passwd|pwd)\b\s*[:=]\s*['\"]?[^,'\"\\s]+"),
    ]

    @classmethod
    def redact_text(cls, value: str) -> str:
        if not isinstance(value, str):
            return value
        redacted = cls.IBAN_RE.sub(REDACTED, value)
        redacted = cls.CARD_RE.sub(REDACTED, redacted)
        for pattern in cls.TOKEN_PATTERNS:
            redacted = pattern.sub(
                lambda m: f"{m.group(1)}={REDACTED}" if m.groups() else REDACTED,
                redacted,
            )
        return redacted

    @classmethod
    def redact(cls, value: Any, key: str | None = None) -> Any:
        if key and key.lower() in cls.SENSITIVE_KEYS:
            return REDACTED
        if value is None:
            return None
        if isinstance(value, str):
            return cls.redact_text(value)
        if isinstance(value, dict):
            return {k: cls.redact(v, key=str(k)) for k, v in value.items()}
        if isinstance(value, (list, tuple, set)):
            return type(value)(cls.redact(v) for v in value)
        return value
