"""VP public summary desensitization — strip PII for export preview."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any, Callable

import yaml

from niannian.tenant.registry import TenantUserRegistry
from niannian.virtual_persona.addressing import extract_profile_calls_user_as
from niannian.virtual_persona.assembly_master import (
    PROACTIVE_ASSEMBLY_EXCERPT_MAX,
    read_assembly_master,
    truncate_assembly_excerpt,
)

if TYPE_CHECKING:
    from niannian.virtual_persona.store import VirtualPersonaStore

REDACTED = "[REDACTED]"
CALLS_USER_PLACEHOLDER = "{calls_user_as}"

LlmDesensitizeFn = Callable[[str], str]

_EMAIL_RE = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
_PHONE_RE = re.compile(r"(?<!\d)(?:\+?86[-\s]?)?1[3-9]\d{9}(?!\d)")
_URL_RE = re.compile(r"https?://[^\s\])>\"']+")
_ATOM_ID_KV_RE = re.compile(r"(?i)\batom[_-]?id\s*[:=]\s*[\w-]+")
_UUID_RE = re.compile(
    r"\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b",
    re.IGNORECASE,
)
def _data_home(store: VirtualPersonaStore) -> Any:
    return store.root.parent.parent


def _load_meta(store: VirtualPersonaStore, vp_id: str) -> dict[str, Any]:
    path = store.vp_dir(vp_id) / "meta.yaml"
    if not path.is_file():
        return {}
    with path.open(encoding="utf-8") as f:
        data = yaml.safe_load(f) or {}
    return data if isinstance(data, dict) else {}


def resolve_calls_user_as(store: VirtualPersonaStore, vp_id: str) -> str:
    meta = _load_meta(store, vp_id)
    raw = str(meta.get("calls_user_as") or "").strip()
    if raw:
        return raw
    profile_md = str(store.get(vp_id).get("profile_md") or "")
    return extract_profile_calls_user_as(profile_md)


def resolve_owner_real_names(store: VirtualPersonaStore, vp_id: str) -> list[str]:
    names: list[str] = []
    meta = _load_meta(store, vp_id)
    for key in ("owner_real_name", "owner_name", "real_name"):
        val = str(meta.get(key) or "").strip()
        if val:
            names.append(val)

    registry = TenantUserRegistry(_data_home(store))
    row = registry.get_user(store.user_id)
    if isinstance(row, dict):
        for key in ("display_name", "real_name", "owner_real_name"):
            val = str(row.get(key) or "").strip()
            if val:
                names.append(val)

    deduped: list[str] = []
    seen: set[str] = set()
    for name in names:
        key = name.casefold()
        if key in seen:
            continue
        seen.add(key)
        deduped.append(name)
    deduped.sort(key=len, reverse=True)
    return deduped


def _replace_owner_names(text: str, owner_names: list[str], calls_user_as: str) -> str:
    replacement = calls_user_as.strip() or CALLS_USER_PLACEHOLDER
    redacted = text
    for name in owner_names:
        if not name or name == replacement:
            continue
        redacted = redacted.replace(name, replacement)
    return redacted


def apply_rule_desensitize(
    text: str,
    *,
    owner_names: list[str] | None = None,
    calls_user_as: str = "",
) -> str:
    redacted = _replace_owner_names(str(text or ""), list(owner_names or []), calls_user_as)
    redacted = _EMAIL_RE.sub(REDACTED, redacted)
    redacted = _PHONE_RE.sub(REDACTED, redacted)
    redacted = _URL_RE.sub(REDACTED, redacted)
    redacted = _ATOM_ID_KV_RE.sub(REDACTED, redacted)
    redacted = _UUID_RE.sub(REDACTED, redacted)
    return redacted


def collect_raw_summary(store: VirtualPersonaStore, vp_id: str) -> str:
    assembly_text = read_assembly_master(store, vp_id)
    return truncate_assembly_excerpt(
        assembly_text,
        max_chars=PROACTIVE_ASSEMBLY_EXCERPT_MAX,
    )


def build_public_summary(
    store: VirtualPersonaStore,
    vp_id: str,
    *,
    llm_fn: LlmDesensitizeFn | None = None,
) -> str:
    """Build desensitized public summary for export preview."""
    raw = collect_raw_summary(store, vp_id)
    calls_user_as = resolve_calls_user_as(store, vp_id)
    owner_names = resolve_owner_real_names(store, vp_id)
    redacted = apply_rule_desensitize(
        raw,
        owner_names=owner_names,
        calls_user_as=calls_user_as,
    )
    if llm_fn is not None:
        return str(llm_fn(redacted) or "")
    return redacted
