"""PII detection and redaction engine.

Zero dependencies. Pure regex + heuristics. Detects common PII patterns
including Nigerian-specific identifiers (NIN, BVN), UK identifiers (NI number),
and universal patterns (email, phone, credit card, DOB, IP).
"""

import re
from dataclasses import dataclass
from typing import Optional


@dataclass
class PIIMatch:
    """A detected PII instance."""
    type: str
    value: str
    start: int
    end: int
    confidence: float  # 0.0 to 1.0


# Pattern definitions: (name, regex, confidence)
_PATTERNS: list[tuple[str, re.Pattern, float]] = [
    (
        "EMAIL",
        re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'),
        0.95,
    ),
    (
        "PHONE_INTL",
        re.compile(r'\+\d{1,3}[\s.-]?\(?\d{1,4}\)?[\s.-]?\d{1,4}[\s.-]?\d{1,9}'),
        0.85,
    ),
    (
        "PHONE_NG",
        re.compile(r'\b0[789][01]\d{8}\b'),
        0.90,
    ),
    (
        "PHONE_UK",
        re.compile(r'\b0\d{4}[\s]?\d{6}\b'),
        0.80,
    ),
    (
        "CREDIT_CARD",
        re.compile(r'\b(?:\d{4}[\s.-]?){3}\d{4}\b'),
        0.80,
    ),
    (
        "NIN_NG",  # Nigerian National Identification Number (11 digits)
        re.compile(r'\b\d{11}\b'),
        0.50,  # Lower confidence — could be other 11-digit numbers
    ),
    (
        "BVN",  # Bank Verification Number (11 digits, same pattern)
        re.compile(r'\b(?:BVN|bvn)[\s:.-]*\d{11}\b'),
        0.95,
    ),
    (
        "NI_NUMBER_UK",  # UK National Insurance number
        re.compile(r'\b[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]\b', re.IGNORECASE),
        0.95,
    ),
    (
        "IP_ADDRESS",
        re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b'),
        0.75,
    ),
    (
        "DATE_OF_BIRTH",
        re.compile(
            r'\b(?:DOB|dob|date of birth|born|birthday)[\s:.-]*'
            r'(\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4})\b',
            re.IGNORECASE,
        ),
        0.90,
    ),
    (
        "DATE_GENERIC",
        re.compile(r'\b\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4}\b'),
        0.30,  # Very low — dates are everywhere, only flag as context
    ),
    (
        "IBAN",
        re.compile(r'\b[A-Z]{2}\d{2}[\s]?[\dA-Z]{4}[\s]?(?:[\dA-Z]{4}[\s]?){1,7}[\dA-Z]{1,4}\b'),
        0.85,
    ),
    (
        "SSN_US",
        re.compile(r'\b\d{3}-\d{2}-\d{4}\b'),
        0.90,
    ),
]

# Redaction labels
_REDACT_MAP = {
    "EMAIL": "[EMAIL_REDACTED]",
    "PHONE_INTL": "[PHONE_REDACTED]",
    "PHONE_NG": "[PHONE_REDACTED]",
    "PHONE_UK": "[PHONE_REDACTED]",
    "CREDIT_CARD": "[CARD_REDACTED]",
    "NIN_NG": "[NIN_REDACTED]",
    "BVN": "[BVN_REDACTED]",
    "NI_NUMBER_UK": "[NI_NUMBER_REDACTED]",
    "IP_ADDRESS": "[IP_REDACTED]",
    "DATE_OF_BIRTH": "[DOB_REDACTED]",
    "DATE_GENERIC": "[DATE_REDACTED]",
    "IBAN": "[IBAN_REDACTED]",
    "SSN_US": "[SSN_REDACTED]",
}


def detect(
    text: str,
    min_confidence: float = 0.5,
    types: Optional[list[str]] = None,
) -> list[PIIMatch]:
    """Detect PII in text.

    Args:
        text: The text to scan.
        min_confidence: Minimum confidence threshold (0.0-1.0).
        types: Only detect these PII types. None = all.

    Returns:
        List of PIIMatch objects, sorted by position.
    """
    matches = []
    for name, pattern, confidence in _PATTERNS:
        if confidence < min_confidence:
            continue
        if types and name not in types:
            continue
        for m in pattern.finditer(text):
            matches.append(PIIMatch(
                type=name,
                value=m.group(),
                start=m.start(),
                end=m.end(),
                confidence=confidence,
            ))

    # Deduplicate overlapping matches — keep highest confidence
    matches.sort(key=lambda x: (x.start, -x.confidence))
    filtered = []
    last_end = -1
    for match in matches:
        if match.start >= last_end:
            filtered.append(match)
            last_end = match.end

    return filtered


def redact(
    text: str,
    min_confidence: float = 0.5,
    types: Optional[list[str]] = None,
) -> str:
    """Detect and redact PII from text.

    Args:
        text: The text to redact.
        min_confidence: Minimum confidence threshold.
        types: Only redact these PII types. None = all.

    Returns:
        Text with PII replaced by redaction labels.
    """
    matches = detect(text, min_confidence=min_confidence, types=types)
    if not matches:
        return text

    # Replace from end to start to preserve positions
    result = text
    for match in reversed(matches):
        label = _REDACT_MAP.get(match.type, f"[{match.type}_REDACTED]")
        result = result[:match.start] + label + result[match.end:]

    return result


def contains_pii(text: str, min_confidence: float = 0.5) -> bool:
    """Quick check: does this text contain PII?"""
    return len(detect(text, min_confidence=min_confidence)) > 0


def summary(text: str, min_confidence: float = 0.5) -> dict[str, int]:
    """Count PII types found in text.

    Returns:
        Dict mapping PII type names to counts.
    """
    matches = detect(text, min_confidence=min_confidence)
    counts: dict[str, int] = {}
    for m in matches:
        counts[m.type] = counts.get(m.type, 0) + 1
    return counts
