"""PII Detection & Redaction for PULSE brain storage.

Standalone module — no PULSE dependencies. 10 pattern types with
Luhn-validated credit cards and private IP exclusion.

Public API:
  - detect_pii(text) -> list[dict]
  - redact_pii(text) -> str
  - contains_pii(text) -> bool
"""

import re

# --- Private IP ranges (excluded from redaction) ---

_PRIVATE_IP_PREFIXES = (
    "127.", "10.", "0.",
    "192.168.",
)
_PRIVATE_172_RANGE = range(16, 32)  # 172.16.0.0 – 172.31.255.255


def _is_private_ip(ip: str) -> bool:
    """Check if IP is RFC 1918 private / loopback."""
    if any(ip.startswith(p) for p in _PRIVATE_IP_PREFIXES):
        return True
    if ip.startswith("172."):
        parts = ip.split(".")
        if len(parts) >= 2:
            try:
                return int(parts[1]) in _PRIVATE_172_RANGE
            except ValueError:
                pass
    return False


# --- Luhn algorithm for credit card validation ---

def _luhn_check(digits: str) -> bool:
    """Validate credit card number via Luhn algorithm."""
    if not digits.isdigit() or len(digits) < 13 or len(digits) > 19:
        return False
    total = 0
    for i, d in enumerate(reversed(digits)):
        n = int(d)
        if i % 2 == 1:
            n *= 2
            if n > 9:
                n -= 9
        total += n
    return total % 10 == 0


# --- Credit card regex pre-filter + Luhn validation ---

_CC_PRE_FILTER = re.compile(r'\b(?:\d[ -]?){12,18}\d\b')


def _find_credit_cards(text: str) -> list[dict]:
    """Find credit card numbers using regex pre-filter + Luhn validation."""
    results = []
    for m in _CC_PRE_FILTER.finditer(text):
        raw = m.group()
        digits = re.sub(r'[ -]', '', raw)
        if _luhn_check(digits):
            results.append({"type": "CREDIT_CARD", "start": m.start(), "end": m.end()})
    return results


# --- PII patterns (regex-based) ---

PII_PATTERNS = {
    "EMAIL": re.compile(r'[\w\.-]+@[\w\.-]+\.\w{2,}'),
    "PHONE": re.compile(r'\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b'),
    "SSN": re.compile(r'\b\d{3}-\d{2}-\d{4}\b'),
    "IP_ADDRESS": re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b'),
    "API_KEY_OPENAI": re.compile(r'sk-[0-9A-Za-z]{20,}'),
    "API_KEY_ANTHROPIC": re.compile(r'sk-ant-[0-9A-Za-z\-]{20,}'),
    "API_KEY_GOOGLE": re.compile(r'AIza[0-9A-Za-z\-_]{35}'),
    "SECRET_PATTERN": re.compile(r'(?:password|secret|token|credential)\s*[:=]\s*\S+', re.I),
}

# SSN must be checked before PHONE (SSN is more specific)
_ORDERED_TYPES = ["SSN", "EMAIL", "API_KEY_OPENAI", "API_KEY_ANTHROPIC",
                  "API_KEY_GOOGLE", "SECRET_PATTERN", "IP_ADDRESS", "PHONE"]


def detect_pii(text: str) -> list[dict]:
    """Return list of {type, start, end} for each PII match found."""
    if not text:
        return []

    hits: list[dict] = []

    # Regex patterns
    for pii_type in _ORDERED_TYPES:
        pattern = PII_PATTERNS[pii_type]
        for m in pattern.finditer(text):
            matched = m.group()
            # IP: skip private ranges
            if pii_type == "IP_ADDRESS" and _is_private_ip(matched):
                continue
            # IP: skip if octets > 255
            if pii_type == "IP_ADDRESS":
                octets = matched.split(".")
                if any(int(o) > 255 for o in octets if o.isdigit()):
                    continue
            hits.append({"type": pii_type, "start": m.start(), "end": m.end()})

    # Credit cards (Luhn-validated)
    hits.extend(_find_credit_cards(text))

    # Sort by position, deduplicate overlapping spans (keep first/longest)
    hits.sort(key=lambda h: (h["start"], -(h["end"] - h["start"])))
    filtered: list[dict] = []
    last_end = -1
    for h in hits:
        if h["start"] >= last_end:
            filtered.append(h)
            last_end = h["end"]

    return filtered


def redact_pii(text: str) -> str:
    """Replace PII with [REDACTED:<TYPE>]. Returns cleaned text."""
    if not text:
        return text

    hits = detect_pii(text)
    if not hits:
        return text

    parts: list[str] = []
    prev = 0
    for h in hits:
        parts.append(text[prev:h["start"]])
        parts.append(f'[REDACTED:{h["type"]}]')
        prev = h["end"]
    parts.append(text[prev:])

    return "".join(parts)


def contains_pii(text: str) -> bool:
    """Quick boolean check for PII presence."""
    return len(detect_pii(text)) > 0
