from __future__ import annotations

from typing import Any, Dict, List, Set, Union

from chatsee.redaction.models import RedactionRule


FieldsToRedact = Union[Set[str], List[str], str, None]
_REDACT_ALL_SENTINELS = {"*", "__all__", "ALL"}


def _mask_value(text: str, rule: RedactionRule) -> str:
    def replacer(match):
        value = match.group()

        if rule.validator and not rule.validator(value):
            return value

        out = []
        for i, c in enumerate(value):
            if not c.isalnum():
                out.append(c)
            elif rule.mask_strategy == "ALTERNATE":
                out.append(rule.mask_char if i % 2 else c)
            else:
                out.append(rule.mask_char)

        return "".join(out)

    return rule.regex.sub(replacer, text)


def _redact_text(text: str, rules: List[RedactionRule]) -> str:
    if not text or len(text) < 6:
        return text

    # cheap performance guard (same as Preprocessor)
    if not any(ch.isdigit() or ch == "@" for ch in text):
        return text

    for rule in rules:
        text = _mask_value(text, rule)

    return text


def _redact_any(value: Any, rules: List[RedactionRule]) -> Any:
    if isinstance(value, str):
        return _redact_text(value, rules)

    if isinstance(value, list):
        return [_redact_any(v, rules) for v in value]

    if isinstance(value, dict):
        return {k: _redact_any(v, rules) for k, v in value.items()}

    return value


def _redact_dict_targeted(
    data: Dict[str, Any], rules: List[RedactionRule], fields_to_redact: FieldsToRedact
) -> Dict[str, Any]:
    redact_all = bool(
        isinstance(fields_to_redact, str) and fields_to_redact in _REDACT_ALL_SENTINELS
    )
    if redact_all:
        return _redact_any(data, rules)

    fields: Set[str] = set()
    if isinstance(fields_to_redact, (list, set, tuple)):
        fields = {str(f).strip() for f in fields_to_redact if str(f).strip()}

    if not fields:
        return data

    out: Dict[str, Any] = data.copy()
    for field in fields:
        if field in out:
            out[field] = _redact_any(out[field], rules)
    return out


def _redact_list_of_dicts(
    data: List[Dict[str, Any]],
    rules: List[RedactionRule],
    fields_to_redact: FieldsToRedact,
) -> List[Dict[str, Any]]:
    redact_all = bool(
        isinstance(fields_to_redact, str) and fields_to_redact in _REDACT_ALL_SENTINELS
    )

    fields: Set[str] = set()
    if isinstance(fields_to_redact, (list, set, tuple)):
        fields = {str(f).strip() for f in fields_to_redact if str(f).strip()}

    redacted: List[Dict[str, Any]] = []

    for item in data:
        if not isinstance(item, dict):
            redacted.append(item)  # type: ignore[arg-type]
            continue

        if redact_all:
            redacted.append(_redact_any(item, rules))
            continue

        if not fields:
            redacted.append(item)
            continue

        new_item: Dict[str, Any] = item.copy()
        for field in fields:
            if field in new_item:
                new_item[field] = _redact_any(new_item[field], rules)
        redacted.append(new_item)

    return redacted


def redact_payload_sync(
    data: Any, rules: List[RedactionRule], fields_to_redact: FieldsToRedact = "*"
) -> Any:
    """
    Redact PII from an arbitrary payload (str/list/dict) using the configured rules.

    - For list[dict], we apply targeted field logic (`fields_to_redact`).
    - For other structures, we recursively redact any string values.
    """
    if not rules:
        return data

    if isinstance(data, list):
        if all(isinstance(item, dict) for item in data):
            return _redact_list_of_dicts(data, rules, fields_to_redact)
        return _redact_any(data, rules)

    if isinstance(data, dict):
        return _redact_dict_targeted(data, rules, fields_to_redact)

    if isinstance(data, str):
        return _redact_text(data, rules)

    return data

