
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Iterable, List, Tuple

from .patterns import (
    EMAIL_RE,
    PHONE_RE,
    AADHAAR_RE,
    PAN_RE,
    IPV4_RE,
    CC_CANDIDATE_RE,
    ADDRESSISH_RE,
)


def _luhn_ok(number: str) -> bool:
    digits = [c for c in number if c.isdigit()]
    if len(digits) < 13 or len(digits) > 19:
        return False

    total = 0
    parity = len(digits) % 2
    for i, ch in enumerate(digits):
        d = ord(ch) - 48
        if i % 2 == parity:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0


@dataclass(frozen=True)
class MaskOptions:
    token: str = "[REDACTED]"
    mask_addressish: bool = False  # keep off by default to reduce false positives


DEFAULT_LABELS = ("EMAIL", "PHONE", "AADHAAR", "PAN", "CREDIT_CARD", "IPV4", "ADDRESSISH")


def mask_text(
    text: str,
    token: str = "[REDACTED]",
    mask_addressish: bool = False,
    *,
    label: bool = True,
) -> str:
    """
    Mask common PII patterns in a text.
    - token: replacement token
    - mask_addressish: heuristic masking for address-like words (off by default)
    - label: if True, output like [REDACTED:EMAIL], else [REDACTED]
    """
    if not isinstance(text, str) or not text:
        return text

    def repl(lbl: str):
        if label:
            return token[:-1] + ":" + lbl + "]" if token.endswith("]") else (token + ":" + lbl)
        return token

    # 1) Direct regex masks
    text = EMAIL_RE.sub(repl("EMAIL"), text)
    text = PHONE_RE.sub(repl("PHONE"), text)
    text = AADHAAR_RE.sub(repl("AADHAAR"), text)
    text = PAN_RE.sub(repl("PAN"), text)
    text = IPV4_RE.sub(repl("IPV4"), text)

    # 2) Credit cards: only mask if Luhn passes
    def cc_replace(match):
        raw = match.group(0)
        digits_only = "".join([c for c in raw if c.isdigit()])
        if _luhn_ok(digits_only):
            return repl("CREDIT_CARD")
        return raw

    text = CC_CANDIDATE_RE.sub(cc_replace, text)

    # 3) Address-ish heuristic (optional)
    if mask_addressish:
        text = ADDRESSISH_RE.sub(repl("ADDRESSISH"), text)

    return text


def mask_dict(
    data: Dict,
    token: str = "[REDACTED]",
    mask_addressish: bool = False,
    *,
    label: bool = True,
    keys_to_mask: Iterable[str] | None = None,
) -> Dict:
    """
    Mask PII inside a dict (common for logs / JSON).
    - keys_to_mask: if given, only mask values for these keys (case-insensitive)
    """
    if not isinstance(data, dict):
        return data

    keyset = None
    if keys_to_mask is not None:
        keyset = set([str(k).lower() for k in keys_to_mask])

    def should_mask_key(k) -> bool:
        if keyset is None:
            return True
        return str(k).lower() in keyset

    out = {}
    for k, v in data.items():
        if isinstance(v, str) and should_mask_key(k):
            out[k] = mask_text(v, token=token, mask_addressish=mask_addressish, label=label)
        elif isinstance(v, dict):
            out[k] = mask_dict(v, token=token, mask_addressish=mask_addressish, label=label, keys_to_mask=keys_to_mask)
        elif isinstance(v, list):
            new_list = []
            for item in v:
                if isinstance(item, str):
                    new_list.append(mask_text(item, token=token, mask_addressish=mask_addressish, label=label))
                elif isinstance(item, dict):
                    new_list.append(mask_dict(item, token=token, mask_addressish=mask_addressish, label=label, keys_to_mask=keys_to_mask))
                else:
                    new_list.append(item)
            out[k] = new_list
        else:
            out[k] = v
    return out