"""Dummy-value generation for sanitize (R-9).

Consistent, role-based pseudonyms; sequential phones and emails by first appearance;
generic addresses; format-preserving masks for SSN and account-type identifiers; and a
single per-document date shift that preserves intervals.
"""
from __future__ import annotations

import datetime
import re
from typing import Optional

from dateutil import parser as _dateparser

from .detectors.structured import DATE_PATTERNS

# Role-based dummy name pools. The same real entity maps to one dummy (handled by the
# Mapping cache); distinct entities in the same role cycle the pool.
NAME_POOLS: dict[str, list[str]] = {
    "petitioner": ["John Smith", "Michael Brown", "Robert Davis", "James Miller",
                   "William Garcia", "Charles Wilson"],
    "respondent": ["Suzie Smith", "Karen Brown", "Linda Davis", "Patricia Miller",
                   "Barbara Garcia", "Nancy Wilson"],
    "testator": ["Eleanor Hayes", "George Hayes", "Margaret Stone", "Walter Stone"],
    "affiant": ["Daniel Reed", "Susan Reed", "Paul Foster", "Janet Foster"],
    "attorney": ["Avery Counsel", "Blake Counsel", "Casey Counsel", "Drew Counsel"],
    "notary": ["Nora Public", "Norman Public", "Nadia Public"],
    "witness": ["Wendy Watts", "Walter Watts", "Wanda Watts"],
    "party": ["Alex Doe", "Jamie Doe", "Casey Roe", "Jordan Roe", "Taylor Poe",
              "Morgan Poe"],
    "unknown": ["Pat Person", "Sam Persons", "Chris Persaud"],
}
ENTITY_POOL = ["Acme Holdings LLC", "Globex Corporation", "Initech Inc",
               "Umbrella Trust", "Vandelay Industries", "Wonka Company"]
DEFAULT_POOL = NAME_POOLS["party"]

# Identifier types replaced with a format-preserving digit mask.
MASK_TYPES = {"ssn", "ein", "bank_account", "routing_number", "payment_card"}


def name_dummy(role: Optional[str], ftype: str, seq: int) -> str:
    pool = ENTITY_POOL if ftype == "entity_name" else NAME_POOLS.get(role or "party",
                                                                     DEFAULT_POOL)
    base = pool[seq % len(pool)]
    wrap = seq // len(pool)
    return base if wrap == 0 else f"{base} {wrap + 1}"


def phone_dummy(seq: int) -> str:
    return f"(555) 555-{seq + 1:04d}"


def email_dummy(seq: int) -> str:
    return f"user{seq + 1}@example.invalid"


def address_dummy(seq: int) -> str:
    return f"{1234 + seq} Main St, Anytown, ST 00000"


def mask_value(real: str) -> str:
    """Replace every digit with X, preserving separators and length."""
    return re.sub(r"\d", "X", real)


def make_assigner(real: str):
    """Build a Mapping assigner closure for one match's real value."""

    def assigner(role: Optional[str], ftype: str, seq: int) -> str:
        if ftype in ("person_name", "entity_name"):
            return name_dummy(role, ftype, seq)
        if ftype == "phone":
            return phone_dummy(seq)
        if ftype == "email":
            return email_dummy(seq)
        if ftype == "address":
            return address_dummy(seq)
        if ftype in MASK_TYPES:
            return mask_value(real)
        return mask_value(real)

    return assigner


def _reformat(dt: datetime.datetime, pattern_index: int) -> str:
    if pattern_index == 0:  # MM/DD/YYYY
        return f"{dt.month:02d}/{dt.day:02d}/{dt.year}"
    if pattern_index == 1:  # YYYY-MM-DD
        return f"{dt.year:04d}-{dt.month:02d}-{dt.day:02d}"
    # Month-name forms.
    return f"{dt.strftime('%B')} {dt.day}, {dt.year}"


def find_dates(text: str) -> list[tuple[int, int, str, int]]:
    """Return (start, end, raw, pattern_index) for each date, deduped by span."""
    hits: dict[tuple[int, int], tuple[int, int, str, int]] = {}
    for idx, pat in enumerate(DATE_PATTERNS):
        for m in pat.finditer(text):
            span = (m.start(), m.end())
            # Keep the first (earliest-index) pattern that claims a span.
            if not any(s <= span[0] and span[1] <= e for (s, e) in hits):
                hits[span] = (span[0], span[1], m.group(0), idx)
    # Drop spans contained in a kept larger span.
    spans = sorted(hits.values(), key=lambda h: (h[0], -(h[1] - h[0])))
    kept: list[tuple[int, int, str, int]] = []
    for h in spans:
        if any(k[0] <= h[0] and h[1] <= k[1] and (k[1] - k[0]) > (h[1] - h[0])
               for k in kept):
            continue
        kept.append(h)
    return kept


def shift_date(raw: str, pattern_index: int, offset_days: int) -> Optional[str]:
    try:
        dt = _dateparser.parse(raw, dayfirst=False, fuzzy=False)
    except (ValueError, OverflowError, TypeError):
        return None
    shifted = dt + datetime.timedelta(days=offset_days)
    return _reformat(shifted, pattern_index)
