"""
Data scrubber — sanitises events before they leave the process.

Two layers of protection:
1.  **Key-based scrubbing** – dictionary keys matching a deny-list are
    replaced with ``[REDACTED]``.
2.  **Pattern-based scrubbing** – regex patterns inside string values
    catch passwords, tokens, JWTs, emails, credit-card numbers, etc.
"""

import re

from overcast_sdk.consts import MAX_SERIALIZE_DEPTH, MAX_SERIALIZE_BREADTH
from overcast_sdk.utils import safe_str

# ── Deny-lists ────────────────────────────────────────────────────────────────

DEFAULT_DENYLIST = [
    "password", "passwd", "pwd", "secret", "token", "auth", "apikey",
    "api_key", "access_key", "private_key", "credential", "credit",
    "card", "ssn", "social", "cookie", "session_id", "sessionid",
    "csrftoken", "csrf_token", "csrf", "authorization", "set_cookie",
    "x_api_key", "x_csrftoken", "mysql_pwd", "privatekey",
]

DEFAULT_PII_DENYLIST = [
    "x_forwarded_for", "x_real_ip", "ip_address", "remote_addr",
    "ip", "user_agent",
]

# ── Regex patterns for string scrubbing ───────────────────────────────────────

_SENSITIVE_KEY_RE = re.compile(
    r"password|passwd|pwd|secret|token|auth|apikey|api_key|access_key|"
    r"private_key|credential|credit|card|ssn|social|cookie|session_id",
    re.IGNORECASE,
)

_STRING_PATTERNS = [
    # password=... or secret: ...
    (re.compile(
        r"\b(password|passwd|pwd|secret|pass)\s*[:=]\s*[\"']?"
        r"([^\s\"',;}\]]+)[\"']?", re.I,
    ), r"\1=[REDACTED]"),
    # Bearer tokens
    (re.compile(r"\b(Bearer)\s+[A-Za-z0-9\-._~+/]+=*", re.I), r"\1 [REDACTED]"),
    # JWTs
    (re.compile(
        r"\beyJ[A-Za-z0-9\-_=]+\.eyJ[A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_.+/=]*"
    ), "[REDACTED_JWT]"),
    # API-style keys
    (re.compile(r"\b(sk_|pk_|api_|key_|token_)[A-Za-z0-9]{16,}", re.I), "[REDACTED_KEY]"),
    # AWS keys
    (re.compile(r"\b(AKIA|ASIA)[A-Z0-9]{16}"), "[REDACTED_AWS]"),
    # Emails
    (re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"), "[REDACTED_EMAIL]"),
    # Phone numbers
    (re.compile(r"(\+\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}"), "[REDACTED_PHONE]"),
    # Credit card numbers
    (re.compile(r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b"), "[REDACTED_CC]"),
    # SSNs
    (re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "[REDACTED_SSN]"),
]

_SENSITIVE_HEADERS = frozenset({
    "authorization", "cookie", "set-cookie", "x-auth-token",
    "x-api-key", "proxy-authorization",
})


# ── Public API ────────────────────────────────────────────────────────────────

class EventScrubber:
    """Scrub sensitive data from event dicts before sending."""

    def __init__(self, send_default_pii=False):
        self._denylist = set(k.lower() for k in DEFAULT_DENYLIST)
        if not send_default_pii:
            self._denylist.update(k.lower() for k in DEFAULT_PII_DENYLIST)

    # -- High-level entry point ------------------------------------------------

    def scrub_event(self, event):
        """Return a scrubbed *copy* of *event* (dict)."""
        return self._scrub_value(event, depth=0)

    # -- Scrubbing logic -------------------------------------------------------

    def _scrub_value(self, data, depth=0):
        if depth > MAX_SERIALIZE_DEPTH:
            return data
        if data is None or isinstance(data, (bool, int, float)):
            return data
        if isinstance(data, str):
            return _scrub_string(data)
        if isinstance(data, list):
            return [
                self._scrub_value(item, depth + 1)
                for item in data[:MAX_SERIALIZE_BREADTH]
            ]
        if isinstance(data, dict):
            out = {}
            for k, v in data.items():
                key_lower = str(k).lower()
                if key_lower in self._denylist or _SENSITIVE_KEY_RE.search(key_lower):
                    out[k] = "[REDACTED]"
                else:
                    out[k] = self._scrub_value(v, depth + 1)
            return out
        return safe_str(data)

    # -- Header scrubbing ------------------------------------------------------

    @staticmethod
    def scrub_headers(headers):
        """Scrub sensitive HTTP headers."""
        if not headers:
            return headers
        return {
            k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v)
            for k, v in headers.items()
        }


# ── Module-level helpers ──────────────────────────────────────────────────────

def _scrub_string(s):
    """Apply all regex-based redactions to a string."""
    for pattern, replacement in _STRING_PATTERNS:
        s = pattern.sub(replacement, s)
    return s
