"""
Offline PII and secrets redaction.

Works without an API key or network connection.
Strips emails, credit cards, API keys, crypto keys, phone numbers, etc.

Usage::

    from agntor import redact

    result = redact("Contact me at john@example.com, my key is AKIAIOSFODNN7EXAMPLE")
    print(result.redacted)
    # "Contact me at [EMAIL], my key is [AWS_KEY]"
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Sequence

from agntor.types import RedactResult, RedactFinding


@dataclass
class _Pattern:
    type: str
    pattern: re.Pattern[str]
    replacement: str


# ── Default redaction patterns (mirrors TypeScript SDK) ───────────────────────

DEFAULT_REDACTION_PATTERNS: list[_Pattern] = [
    # Emails
    _Pattern("email", re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"), "[EMAIL]"),
    # Credit Cards
    _Pattern("credit_card", re.compile(r"\b(?:\d[ -]*?){13,16}\b"), "[CREDIT_CARD]"),
    # IPv4 Addresses
    _Pattern("ipv4", re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "[IP_ADDRESS]"),
    # US SSN
    _Pattern("ssn", re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "[SSN]"),
    # AWS Access Key ID
    _Pattern("aws_access_key", re.compile(r"\bAKIA[0-9A-Z]{16}\b"), "[AWS_KEY]"),
    # Generic Secret/API Key
    _Pattern(
        "api_key",
        re.compile(r"\b(api_key|secret|password|token)\s*[:=]\s*[\"']?[a-zA-Z0-9\-_]{20,}[\"']?", re.I),
        r"\1: [REDACTED]",
    ),
    # Bearer Token
    _Pattern("bearer_token", re.compile(r"bearer\s+[a-zA-Z0-9._\-/+=]{20,}", re.I), "Bearer [REDACTED]"),
    # Phone Numbers
    _Pattern(
        "phone_number",
        re.compile(r"\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b"),
        "[PHONE]",
    ),
    # Ethereum / EVM private key (64 hex chars)
    _Pattern("private_key", re.compile(r"\b(?:0x)?[0-9a-fA-F]{64}\b"), "[PRIVATE_KEY]"),
    # BIP-39 Mnemonic Seed Phrase (12 words)
    _Pattern("mnemonic_seed", re.compile(r"\b(?:[a-z]{3,8}\s){11}[a-z]{3,8}\b"), "[MNEMONIC_12]"),
    # BIP-39 Mnemonic Seed Phrase (24 words)
    _Pattern("mnemonic_seed", re.compile(r"\b(?:[a-z]{3,8}\s){23}[a-z]{3,8}\b"), "[MNEMONIC_24]"),
    # Solana private key (base58, 87-88 chars)
    _Pattern("solana_private_key", re.compile(r"\b[1-9A-HJ-NP-Za-km-z]{87,88}\b"), "[SOLANA_PRIVATE_KEY]"),
    # Bitcoin WIF private key
    _Pattern("btc_wif_key", re.compile(r"\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b"), "[BTC_PRIVATE_KEY]"),
    # Ethereum keystore JSON
    _Pattern(
        "keystore_json",
        re.compile(r'"ciphertext"\s*:\s*"[0-9a-fA-F]{64,}"'),
        '"ciphertext": "[REDACTED_KEYSTORE]"',
    ),
    # HD derivation path
    _Pattern("hd_path", re.compile(r"\bm/\d+'?/\d+'?/\d+'?(?:/\d+'?){0,2}\b"), "[HD_PATH]"),
]


def redact(
    text: str,
    *,
    extra_patterns: Sequence[_Pattern] | None = None,
) -> RedactResult:
    """
    Redact PII and secrets from text.

    This is a pure local function -- no network calls, no API key needed.

    Args:
        text: The input text to redact.
        extra_patterns: Additional patterns beyond the defaults.

    Returns:
        RedactResult with the redacted text and list of findings.
    """
    all_patterns = list(DEFAULT_REDACTION_PATTERNS)
    if extra_patterns:
        all_patterns.extend(extra_patterns)

    # Collect all matches
    matches: list[tuple[int, int, str, str, str]] = []  # (start, end, type, replacement, value)

    for p in all_patterns:
        for m in p.pattern.finditer(text):
            # Handle backreferences in replacement
            replacement = m.expand(p.replacement)
            matches.append((m.start(), m.end(), p.type, replacement, m.group(0)))

    # Sort by position, then longest match first for overlaps
    matches.sort(key=lambda x: (x[0], -(x[1] - x[0])))

    # Build output, skipping overlapping matches
    findings: list[RedactFinding] = []
    cursor = 0
    parts: list[str] = []

    for start, end, ptype, replacement, value in matches:
        if start < cursor:
            continue  # Skip overlapping match
        parts.append(text[cursor:start])
        parts.append(replacement)
        findings.append(RedactFinding(type=ptype, span=(start, end), value=value))
        cursor = end

    parts.append(text[cursor:])

    return RedactResult(redacted="".join(parts), findings=findings)
