"""Guards and helpers for workspace profile markdown files (USER, PERSONA, IDENTITY, SOUL)."""
from __future__ import annotations

import os
import re
from contextlib import contextmanager
from contextvars import ContextVar
from datetime import date
from typing import Iterator

from core.config import parse_identity_name_from_markdown

PROFILE_BASENAMES: frozenset[str] = frozenset(
    {"USER.MD", "IDENTITY.MD", "SOUL.MD", "PERSONA.MD"}
)

_PERSONA_NAME_BASENAMES: frozenset[str] = frozenset({"IDENTITY.MD", "SOUL.MD", "PERSONA.MD"})

_SENSITIVE_USER_FIELDS: frozenset[str] = frozenset(
    {
        "partner",
        "spouse",
        "wife",
        "husband",
        "children",
        "child",
        "family",
        "parent",
        "mother",
        "father",
    }
)

_USER_PROFILE_FIELDS: tuple[str, ...] = (
    "Name",
    "What to call them",
    "Pronouns",
    "Timezone",
    "Preferred tone",
    "Response length preference",
)

_FIELD_LINE_RE = re.compile(r"^\s*-\s*\*\*(?P<field>[^:]+):\*\*\s*(?P<rest>.*)$")

_patch_user_profile_active: ContextVar[bool] = ContextVar("_patch_user_profile_active", default=False)


@contextmanager
def patch_user_profile_write_context() -> Iterator[None]:
    """Allow patch_user_profile to merge USER.md while global append/overwrite guards apply."""
    token = _patch_user_profile_active.set(True)
    try:
        yield
    finally:
        _patch_user_profile_active.reset(token)


def is_profile_basename(basename: str) -> bool:
    return (basename or "").upper() in PROFILE_BASENAMES


def bootstrap_exists(workspace_dir: str) -> bool:
    ws = (workspace_dir or "").strip()
    if not ws:
        return False
    return any(os.path.isfile(os.path.join(ws, n)) for n in ("BOOTSTRAP.md", "BOOTSTRAP.MD"))


def is_onboarding_active(workspace_dir: str, config: dict | None) -> bool:
    if bootstrap_exists(workspace_dir):
        return True
    ob = (config or {}).get("onboarding") if isinstance(config, dict) else None
    if isinstance(ob, dict) and ob.get("done") is True:
        return False
    return True


def _restricted_background_writes() -> bool:
    try:
        from tool_policy import is_non_foreground_personal_context

        return is_non_foreground_personal_context()
    except Exception:
        return False


def _file_has_real_profile_content(path: str) -> bool:
    try:
        from persistence import _identity_file_has_real_content

        return _identity_file_has_real_content(path)
    except Exception:
        # Fail closed: if we cannot read the file, treat it as populated.
        return True


def _content_sets_mugi_persona_name(content: str, basename_upper: str) -> bool:
    if basename_upper not in _PERSONA_NAME_BASENAMES:
        return False
    name = parse_identity_name_from_markdown(content or "")
    return bool(name and name.strip().lower() == "mugi")


def check_profile_write_allowed(
    basename: str,
    abs_path: str,
    content: str,
    *,
    workspace_dir: str,
    config: dict | None,
    mode: str = "overwrite",
) -> str | None:
    """Return an error message if the profile write must be refused, else None."""
    base_upper = (basename or os.path.basename(abs_path or "")).upper()
    if base_upper not in PROFILE_BASENAMES:
        return None

    if _patch_user_profile_active.get() and base_upper == "USER.MD" and mode == "overwrite":
        return None

    if _restricted_background_writes():
        return (
            f"Refused: cannot write to {basename} during background, idle, scheduled, "
            "messaging, mesh, hook, or subagent runs. "
            "Profile files are user-owned; use Settings → Profile or a main chat session."
        )

    onboarding = is_onboarding_active(workspace_dir, config)

    if mode == "append":
        if onboarding:
            return None
        return (
            f"Refused: cannot append to {basename} outside onboarding. "
            "Use patch_user_profile for user-stated facts or Settings → Profile."
        )

    if _content_sets_mugi_persona_name(content, base_upper) and not onboarding:
        return (
            "Refused: 'Mugi' is the product name, not your persona name. "
            "Set your assistant name in Settings → Profile."
        )

    if onboarding:
        return None

    if os.path.isfile(abs_path) and _file_has_real_profile_content(abs_path):
        if base_upper == "USER.MD":
            return (
                "Refused: USER.md already has content. Use patch_user_profile for facts the user "
                "explicitly stated, or Settings → Profile."
            )
        return (
            f"Refused: {basename} already has content. Persona files are user-owned after onboarding; "
            "use Settings → Profile."
        )

    return None


def should_inject_user_md_nudge(workspace_dir: str) -> str:
    """Passive onboarding-only hint; never pressure the model to fill USER.md."""
    if not bootstrap_exists(workspace_dir):
        return ""
    return (
        "\n\nDuring onboarding, USER.md may be sparse. Ask naturally if needed; "
        "never invent biographical details.\n"
    )


def field_is_sensitive(field: str) -> bool:
    key = (field or "").strip().lower()
    if not key:
        return False
    if key in _SENSITIVE_USER_FIELDS:
        return True
    return any(tok in key for tok in _SENSITIVE_USER_FIELDS)


def normalize_user_profile_field(field: str) -> str | None:
    raw = (field or "").strip()
    if not raw:
        return None
    low = raw.lower()
    for canonical in _USER_PROFILE_FIELDS:
        if low == canonical.lower():
            return canonical
    if low in ("important notes", "notes", "note"):
        return "Important Notes"
    return raw


def _field_value_empty(rest: str) -> bool:
    v = (rest or "").strip()
    return not v or v in ("—", "-")


def merge_user_profile_field(existing: str, field: str, value: str) -> str:
    """Merge one USER.md field; append to Important Notes if field already populated."""
    field_norm = normalize_user_profile_field(field) or field.strip()
    val = (value or "").strip()
    if not val:
        raise ValueError("value must be non-empty")

    text = existing if (existing or "").strip() else "# USER.md - About Your Human\n"
    lines = text.splitlines()
    bullet_re = re.compile(
        rf"^\s*-\s*\*\*{re.escape(field_norm)}:\*\*",
        re.IGNORECASE,
    )

    if field_norm == "Important Notes":
        return _append_important_note(text, val)

    for idx, line in enumerate(lines):
        if not bullet_re.match(line):
            continue
        m = _FIELD_LINE_RE.match(line)
        if not m:
            continue
        rest = m.group("rest")
        if _field_value_empty(rest):
            lines[idx] = f"- **{field_norm}:** {val}"
            return "\n".join(lines).rstrip() + "\n"
        note = f"- ({date.today().isoformat()}) {field_norm}: {val}"
        return _append_important_note("\n".join(lines), note)

    lines.append(f"- **{field_norm}:** {val}")
    return "\n".join(lines).rstrip() + "\n"


def _append_important_note(text: str, note_body: str) -> str:
    lines = text.splitlines()
    heading = "## Important Notes"
    bullet = note_body if note_body.lstrip().startswith("-") else f"- {note_body}"
    for idx, line in enumerate(lines):
        if line.strip().lower() == heading.lower():
            insert_at = idx + 1
            while insert_at < len(lines) and not lines[insert_at].strip():
                insert_at += 1
            lines.insert(insert_at, bullet)
            return "\n".join(lines).rstrip() + "\n"
    if lines and lines[-1].strip():
        lines.append("")
    lines.extend([heading, "", bullet])
    return "\n".join(lines).rstrip() + "\n"


def sensitive_field_allowed(value: str, recent_user_text: str) -> bool:
    val = (value or "").strip()
    if not val:
        return False
    hay = (recent_user_text or "").strip()
    if not hay:
        return False
    return val.lower() in hay.lower()


def build_recent_user_text(
    messages: list[dict] | None,
    *,
    max_turns: int = 5,
    max_chars: int = 4000,
) -> str:
    """Concatenate recent real user message text for profile patch verification."""
    from utils.transcript_roles import is_real_user_message

    chunks: list[str] = []
    for msg in reversed(messages or []):
        if not isinstance(msg, dict) or not is_real_user_message(msg):
            continue
        content = msg.get("content")
        if isinstance(content, str) and content.strip():
            chunks.append(content.strip())
        elif isinstance(content, list):
            parts: list[str] = []
            for part in content:
                if isinstance(part, dict) and part.get("type") == "text":
                    t = str(part.get("text") or "").strip()
                    if t:
                        parts.append(t)
            if parts:
                chunks.append("\n".join(parts))
        if len(chunks) >= max_turns:
            break
    joined = "\n\n".join(reversed(chunks))
    return joined[:max_chars]
