"""Structured PII detectors: regex plus checksums (R-2).

Detects SSN, EIN, bank account and routing numbers, payment cards (Luhn-validated),
dates of birth, phone numbers, email addresses, and court case numbers. Account and
routing detection is context-anchored to keep the clean-fixture false-positive count
at zero; bare digit runs are not treated as account numbers.
"""
from __future__ import annotations

import re
from typing import Iterator

from .base import Match, aba_valid, luhn_valid, mask_digits, mask_email, mask_middle

# --- date sub-patterns, reused by DOB here and by the date shifter in sanitize ---
_MONTHS = (
    r"Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|"
    r"Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?"
)
DATE_PATTERNS = [
    re.compile(r"\b\d{1,2}/\d{1,2}/\d{2,4}\b"),
    re.compile(r"\b\d{4}-\d{1,2}-\d{1,2}\b"),
    re.compile(rf"\b(?:{_MONTHS})\.?\s+\d{{1,2}},?\s+\d{{4}}\b", re.IGNORECASE),
    re.compile(rf"\b\d{{1,2}}\s+(?:{_MONTHS})\.?,?\s+\d{{4}}\b", re.IGNORECASE),
]

_SSN_RE = re.compile(r"\b(\d{3})[- ](\d{2})[- ](\d{4})\b")
_EIN_RE = re.compile(r"\b(\d{2})-(\d{7})\b")
_EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
_PHONE_RE = re.compile(
    r"(?<!\d)(?:\+?1[-.\s]?)?(?:\(\d{3}\)\s*|\d{3}[-.\s])\d{3}[-.\s]\d{4}(?!\d)"
)
_CARD_RE = re.compile(r"(?<!\d)(?:\d[ -]?){12,18}\d(?!\d)")
_ROUTING_RE = re.compile(
    r"(?i)(?:routing|aba|rtn|transit)\D{0,20}(\d{9})\b"
)
_ACCOUNT_RE = re.compile(
    r"(?i)(?:account|acct|a/c)\b\D{0,20}(\d{6,17})\b"
)
_STREET_SUFFIX = (
    r"Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Drive|Dr|Lane|Ln|Way|Court|Ct|"
    r"Place|Pl|Terrace|Ter|Circle|Cir|Highway|Hwy|Parkway|Pkwy|Trail|Trl"
)
_ADDRESS_RE = re.compile(
    rf"\b\d{{1,6}}\s+(?:[A-Z][A-Za-z.]+\s+){{1,4}}(?:{_STREET_SUFFIX})\b\.?"
    rf"(?:,\s*[A-Z][A-Za-z]+)?(?:,?\s*[A-Z]{{2}}\s+\d{{5}}(?:-\d{{4}})?)?"
)
_DOB_RE = re.compile(
    rf"(?i)(?:d\.?o\.?b\.?|date of birth|born(?:\s+on)?)\D{{0,15}}"
    rf"((?:\d{{1,2}}/\d{{1,2}}/\d{{2,4}})|(?:\d{{4}}-\d{{1,2}}-\d{{1,2}})|"
    rf"(?:(?:{_MONTHS})\.?\s+\d{{1,2}},?\s+\d{{4}}))"
)
# Court / case numbers: Missouri-style, federal-style, and "Case No." anchored.
_CASE_PATTERNS = [
    re.compile(r"\b\d{2}[A-Z]{2}-[A-Z]{2}\d{4,6}\b"),
    re.compile(r"\b\d{1,2}:\d{2}-[a-z]{2}-\d{3,6}(?:-[A-Z]{2,4})?\b"),
    re.compile(
        r"(?i)(?:case|cause|docket)\s+(?:no\.?|number|#)\s*:?\s*([A-Za-z0-9][A-Za-z0-9:\-]{4,})"
    ),
]


def _ssn_valid(area: str, group: str, serial: str) -> bool:
    a = int(area)
    if a == 0 or a == 666 or a >= 900:
        return False
    if int(group) == 0 or int(serial) == 0:
        return False
    return True


def detect_ssn(text: str) -> Iterator[Match]:
    for m in _SSN_RE.finditer(text):
        if not _ssn_valid(m.group(1), m.group(2), m.group(3)):
            continue
        raw = m.group(0)
        yield Match("structured.ssn", "ssn", m.start(), m.end(), raw,
                    preview=mask_digits(raw, keep_last=4))


def detect_ein(text: str) -> Iterator[Match]:
    for m in _EIN_RE.finditer(text):
        raw = m.group(0)
        yield Match("structured.ein", "ein", m.start(), m.end(), raw,
                    preview=mask_digits(raw, keep_last=4))


def detect_routing(text: str) -> Iterator[Match]:
    for m in _ROUTING_RE.finditer(text):
        digits = m.group(1)
        confidence = 0.95 if aba_valid(digits) else 0.7
        yield Match("structured.routing", "routing_number", m.start(1), m.end(1),
                    digits, preview=mask_digits(digits, keep_last=4),
                    confidence=confidence,
                    detail={"aba_checksum": aba_valid(digits)})


def detect_account(text: str) -> Iterator[Match]:
    for m in _ACCOUNT_RE.finditer(text):
        digits = m.group(1)
        yield Match("structured.account", "bank_account", m.start(1), m.end(1),
                    digits, preview=mask_digits(digits, keep_last=4))


def detect_card(text: str) -> Iterator[Match]:
    for m in _CARD_RE.finditer(text):
        raw = m.group(0)
        digits = re.sub(r"\D", "", raw)
        if not (13 <= len(digits) <= 19):
            continue
        if not luhn_valid(digits):
            continue
        yield Match("structured.card", "payment_card", m.start(), m.end(), raw,
                    preview=mask_digits(raw, keep_last=4),
                    detail={"luhn": True})


def detect_dob(text: str) -> Iterator[Match]:
    for m in _DOB_RE.finditer(text):
        raw = m.group(1)
        yield Match("structured.dob", "dob", m.start(1), m.end(1), raw,
                    preview=mask_middle(raw, keep_ends=2))


def detect_phone(text: str) -> Iterator[Match]:
    for m in _PHONE_RE.finditer(text):
        raw = m.group(0)
        yield Match("structured.phone", "phone", m.start(), m.end(), raw,
                    preview=mask_digits(raw, keep_last=4))


def detect_email(text: str) -> Iterator[Match]:
    for m in _EMAIL_RE.finditer(text):
        raw = m.group(0)
        yield Match("structured.email", "email", m.start(), m.end(), raw,
                    preview=mask_email(raw))


def detect_address(text: str) -> Iterator[Match]:
    for m in _ADDRESS_RE.finditer(text):
        raw = m.group(0).rstrip(" .")
        yield Match("structured.address", "address", m.start(), m.start() + len(raw),
                    raw, preview=mask_middle(raw, keep_ends=4))


def detect_case_number(text: str) -> Iterator[Match]:
    seen: set[tuple[int, int]] = set()
    for pat in _CASE_PATTERNS:
        for m in pat.finditer(text):
            grp = 1 if m.groups() else 0
            span = (m.start(grp), m.end(grp))
            if span in seen:
                continue
            seen.add(span)
            raw = m.group(grp)
            yield Match("structured.case_number", "case_number", span[0], span[1],
                        raw, preview=raw)


# Ordered list of all structured detectors.
ALL_DETECTORS = [
    detect_ssn,
    detect_ein,
    detect_routing,
    detect_account,
    detect_card,
    detect_dob,
    detect_phone,
    detect_email,
    detect_address,
    detect_case_number,
]


def detect_all(text: str) -> list[Match]:
    out: list[Match] = []
    for det in ALL_DETECTORS:
        out.extend(det(text))
    return out
