"""Parse and render workspace profile markdown (PERSONA.md + USER.md)."""
from __future__ import annotations

import os
import re
from typing import Any

from core.config import parse_identity_name_from_markdown

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


def _read(path: str) -> str:
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except OSError:
        return ""


def _parse_fields(text: str) -> dict[str, str]:
    out: dict[str, str] = {}
    for line in (text or "").splitlines():
        m = _FIELD_LINE_RE.match(line.strip())
        if not m:
            continue
        key = (m.group("field") or "").strip()
        val = (m.group("value") or "").strip()
        if key:
            out[key] = val
    return out


def _notes_section(text: str) -> str:
    lines = (text or "").splitlines()
    capture = False
    buf: list[str] = []
    for line in lines:
        if line.strip().lower() == "## important notes":
            capture = True
            continue
        if capture and line.startswith("## ") and line.strip().lower() != "## important notes":
            break
        if capture:
            buf.append(line)
    return "\n".join(buf).strip()


def parse_persona_md(text: str) -> dict[str, str]:
    fields = _parse_fields(text)
    return {
        "name": fields.get("Name", ""),
        "creature": fields.get("Creature", ""),
        "vibe": fields.get("Vibe", ""),
        "emoji": fields.get("Emoji", ""),
    }


def parse_user_md(text: str) -> dict[str, str]:
    fields = _parse_fields(text)
    return {
        "name": fields.get("Name", ""),
        "call_as": fields.get("What to call them", ""),
        "pronouns": fields.get("Pronouns", ""),
        "timezone": fields.get("Timezone", ""),
        "tone": fields.get("Preferred tone", ""),
        "notes": _notes_section(text),
    }


def render_persona_md(assistant: dict[str, Any], *, existing: str | None = None) -> str:
    a = assistant if isinstance(assistant, dict) else {}
    name = str(a.get("name") or "").strip()
    creature = str(a.get("creature") or "").strip()
    vibe = str(a.get("vibe") or "").strip()
    emoji = str(a.get("emoji") or "").strip()
    base = (existing or "").strip()
    if not base:
        try:
            from persistence import _read_template

            base = _read_template("PERSONA.md")
        except Exception:
            base = "# PERSONA.md - Who you are\n\n## Identity\n"
    lines = base.splitlines()
    out: list[str] = []
    field_map = {
        "Name": name,
        "Creature": creature,
        "Vibe": vibe,
        "Emoji": emoji,
    }
    for line in lines:
        m = _FIELD_LINE_RE.match(line.strip())
        if m:
            fld = (m.group("field") or "").strip()
            if fld in field_map:
                out.append(f"- **{fld}:** {field_map[fld]}")
                continue
        out.append(line)
    return "\n".join(out).rstrip() + "\n"


def _replace_notes_section(text: str, notes: str) -> str:
    """Replace the body of ## Important Notes, preserving other sections."""
    lines = (text or "").splitlines()
    heading = "## Important Notes"
    start: int | None = None
    end = len(lines)
    for idx, line in enumerate(lines):
        if line.strip().lower() == heading.lower():
            start = idx
            break
    if start is None:
        if not notes.strip():
            return text.rstrip() + "\n" if text else ""
        note_line = notes if notes.lstrip().startswith("-") else f"- {notes}"
        suffix = f"\n{heading}\n\n{note_line}\n"
        return (text.rstrip() + suffix) if text.strip() else f"# USER.md - About Your Human\n\n{heading}\n\n{note_line}\n"

    for idx in range(start + 1, len(lines)):
        if lines[idx].startswith("## ") and lines[idx].strip().lower() != heading.lower():
            end = idx
            break

    note_lines: list[str] = []
    if notes.strip():
        note_line = notes if notes.lstrip().startswith("-") else f"- {notes}"
        note_lines = ["", note_line]
    elif start + 1 < end:
        note_lines = [""]

    new_lines = lines[: start + 1] + note_lines + lines[end:]
    return "\n".join(new_lines).rstrip() + "\n"


def render_user_md(user: dict[str, Any], *, existing: str | None = None) -> str:
    u = user if isinstance(user, dict) else {}
    base = (existing or "").strip()
    if not base:
        try:
            from persistence import _read_template

            base = _read_template("USER.md")
        except Exception:
            base = "# USER.md - About Your Human\n"
    field_map = {
        "Name": str(u.get("name") or "").strip(),
        "What to call them": str(u.get("call_as") or "").strip(),
        "Pronouns": str(u.get("pronouns") or "").strip(),
        "Timezone": str(u.get("timezone") or "").strip(),
        "Preferred tone": str(u.get("tone") or "").strip(),
    }
    lines = base.splitlines()
    out: list[str] = []
    for line in lines:
        m = _FIELD_LINE_RE.match(line.strip())
        if m:
            fld = (m.group("field") or "").strip()
            if fld in field_map:
                out.append(f"- **{fld}:** {field_map[fld]}")
                continue
        out.append(line)
    text = "\n".join(out).rstrip() + "\n"
    notes = str(u.get("notes") or "").strip()
    return _replace_notes_section(text, notes)


def load_profile_from_workspace(workspace: str) -> dict[str, Any]:
    ws = os.path.expanduser(workspace or "")
    persona_path = os.path.join(ws, "PERSONA.md")
    user_path = os.path.join(ws, "USER.md")
    id_path = os.path.join(ws, "IDENTITY.md")
    soul_path = os.path.join(ws, "SOUL.md")

    persona_text = _read(persona_path) if os.path.isfile(persona_path) else ""
    if not persona_text.strip() and os.path.isfile(id_path):
        persona_text = _read(id_path)
        if os.path.isfile(soul_path):
            soul = _read(soul_path).strip()
            if soul:
                persona_text = persona_text.rstrip() + "\n\n## Soul\n\n" + soul

    assistant = parse_persona_md(persona_text)
    if not assistant.get("name") and persona_text:
        parsed = parse_identity_name_from_markdown(persona_text)
        if parsed:
            assistant["name"] = parsed

    user = parse_user_md(_read(user_path) if os.path.isfile(user_path) else "")
    return {"assistant": assistant, "user": user}


def save_profile_to_workspace(workspace: str, payload: dict[str, Any]) -> None:
    import logging

    from persistence import invalidate_personality_cache, secure_atomic_write

    _log = logging.getLogger(__name__)
    ws = os.path.expanduser(workspace or "")
    assistant = payload.get("assistant") if isinstance(payload.get("assistant"), dict) else {}
    user = payload.get("user") if isinstance(payload.get("user"), dict) else {}
    persona_path = os.path.join(ws, "PERSONA.md")
    user_path = os.path.join(ws, "USER.md")
    persona_existing = _read(persona_path) if os.path.isfile(persona_path) else ""
    user_existing = _read(user_path) if os.path.isfile(user_path) else ""
    secure_atomic_write(persona_path, render_persona_md(assistant, existing=persona_existing))
    secure_atomic_write(user_path, render_user_md(user, existing=user_existing))
    name = str(assistant.get("name") or "").strip()
    if name:
        try:
            from core.config import get_config, save_config

            cfg = get_config()
            cfg.setdefault("general", {})["name"] = name
            save_config(cfg)
        except Exception as exc:
            _log.warning("save_profile_to_workspace: config name sync failed: %s", exc)
    invalidate_personality_cache()


def extract_markdown_section(
    text: str,
    heading: str,
    *,
    level: int = 2,
    max_chars: int | None = None,
) -> str:
    """Return body of ``## heading`` (or ###) until the next same-or-higher heading."""
    if not (text or "").strip():
        return ""
    target = heading.strip().lower()
    lines = text.splitlines()
    start: int | None = None
    end = len(lines)
    for idx, line in enumerate(lines):
        stripped = line.strip()
        if not stripped.startswith("#"):
            continue
        level_count = len(stripped) - len(stripped.lstrip("#"))
        title = stripped[level_count:].strip().lower()
        if start is None:
            if level_count == level and title == target:
                start = idx + 1
            continue
        if level_count <= level and title != target:
            end = idx
            break
    if start is None:
        return ""
    body = "\n".join(lines[start:end]).strip()
    if max_chars is not None and len(body) > max_chars:
        body = body[:max_chars] + f"\n… [truncated — read_file for full {heading}]"
    return body


def _heading_line_index(lines: list[str], heading: str, level: int) -> int | None:
    target = heading.strip().lower()
    for idx, line in enumerate(lines):
        stripped = line.strip()
        if not stripped.startswith("#"):
            continue
        level_count = len(stripped) - len(stripped.lstrip("#"))
        if level_count == level and stripped[level_count:].strip().lower() == target:
            return idx
    return None


def _section_body_end(lines: list[str], heading_idx: int, heading_level: int) -> int:
    for idx in range(heading_idx + 1, len(lines)):
        stripped = lines[idx].strip()
        if not stripped.startswith("#"):
            continue
        level_count = len(stripped) - len(stripped.lstrip("#"))
        if level_count <= heading_level:
            return idx
    return len(lines)


def _extract_soul_through_continuity(persona_text: str) -> str:
    """From ``## Soul`` through end of ``Continuity`` (``##`` or ``###``).

    Evolved workspaces often use sibling ``## Core Truths`` / ``## Boundaries`` /
    ``## Growth`` between Soul and Continuity — a single ``## Soul`` section
    extract would drop them.
    """
    lines = persona_text.splitlines()
    soul_idx = _heading_line_index(lines, "Soul", 2)
    if soul_idx is None:
        return ""
    cont_idx = _heading_line_index(lines, "Continuity", 2)
    cont_level = 2
    if cont_idx is None:
        cont_idx = _heading_line_index(lines, "Continuity", 3)
        cont_level = 3
    if cont_idx is not None and cont_idx >= soul_idx:
        end = _section_body_end(lines, cont_idx, cont_level)
        return "\n".join(lines[soul_idx:end]).strip()
    end = _section_body_end(lines, soul_idx, 2)
    return "\n".join(lines[soul_idx:end]).strip()


def _extract_persona_lazy_core(persona_text: str, *, max_chars: int) -> str:
    """Identity fields + Soul through Continuity subsection."""
    if not persona_text.strip():
        return ""
    identity = extract_markdown_section(persona_text, "Identity", level=2, max_chars=None)
    soul_block = _extract_soul_through_continuity(persona_text)
    chunks: list[str] = []
    if identity:
        chunks.append("## Identity\n\n" + identity)
    if soul_block:
        chunks.append(soul_block)
    if not chunks and persona_text.strip():
        chunks.append(persona_text.strip()[:max_chars])
    out = "\n\n".join(chunks).strip()
    if len(out) > max_chars:
        out = out[:max_chars] + "\n… [truncated — read_file('PERSONA.md') for full soul]"
    return out


def _render_user_lazy_md(user: dict[str, Any], *, notes_cap: int) -> str:
    u = user if isinstance(user, dict) else {}
    lines = [
        "# USER.md - About Your Human",
        "",
        "## Important Notes",
        "",
        f"- **Name:** {str(u.get('name') or '').strip()}",
        f"- **What to call them:** {str(u.get('call_as') or '').strip()}",
        f"- **Pronouns:** {str(u.get('pronouns') or '').strip()}",
        f"- **Timezone:** {str(u.get('timezone') or '').strip()}",
        f"- **Preferred tone:** {str(u.get('tone') or '').strip()}",
    ]
    notes = str(u.get("notes") or "").strip()
    if notes:
        if len(notes) > notes_cap:
            notes = notes[:notes_cap] + "… [truncated]"
        note_line = notes if notes.lstrip().startswith("-") else f"- {notes}"
        lines.extend(["", note_line])
    return "\n".join(lines).rstrip() + "\n"


def build_lazy_personality_block(
    workspace: str,
    config,
    files: list[str] | None = None,
    *,
    sync_config: dict | None = None,
    persona_soul_cap: int = 1500,
    user_notes_cap: int = 500,
    agents_standing_cap: int = 2000,
) -> str:
    """Assemble capped personality inject for main chat (lazy mode)."""
    ws = os.path.expanduser(workspace or "")
    file_list = list(files) if isinstance(files, list) else []
    if not file_list and config:
        file_list = (config.get("personality_files") or {}).get("files") or []

    profile = load_profile_from_workspace(ws)
    parts: list[str] = []

    persona_path = os.path.join(ws, "PERSONA.md")
    id_path = os.path.join(ws, "IDENTITY.md")
    soul_path = os.path.join(ws, "SOUL.md")
    persona_text = _read(persona_path) if os.path.isfile(persona_path) else ""
    if not persona_text.strip() and os.path.isfile(id_path):
        persona_text = _read(id_path)
        if os.path.isfile(soul_path):
            soul = _read(soul_path).strip()
            if soul:
                persona_text = persona_text.rstrip() + "\n\n## Soul\n\n" + soul

    if sync_config is not None and persona_text.strip():
        name_val = parse_identity_name_from_markdown(persona_text)
        if name_val:
            sync_config.setdefault("general", {})["name"] = name_val

    for filename in file_list:
        if not filename or not isinstance(filename, str):
            continue
        base = os.path.basename(filename.strip())
        upper = base.upper()
        if upper == "MEMORY.MD":
            continue
        if upper == "PERSONA.MD":
            core = _extract_persona_lazy_core(persona_text, max_chars=persona_soul_cap)
            label = "─── PERSONA.md ───"
            if core.strip():
                parts.append(f"{label}\n{core}\n")
            else:
                parts.append(f"{label}\n<file does not exist yet>\n")
            continue
        if upper == "USER.MD":
            user_md = _render_user_lazy_md(profile.get("user") or {}, notes_cap=user_notes_cap)
            parts.append(f"─── USER.md ───\n{user_md}\n")
            continue
        if upper == "AGENTS.MD":
            agents_path = os.path.join(ws, "AGENTS.md")
            agents_text = _read(agents_path) if os.path.isfile(agents_path) else ""
            standing = extract_markdown_section(
                agents_text, "Standing Orders", level=2, max_chars=agents_standing_cap
            )
            label = "─── AGENTS.md ───"
            if standing.strip():
                parts.append(f"{label}\n## Standing Orders\n\n{standing}\n")
            elif agents_text.strip():
                snippet = agents_text.strip()
                if len(snippet) > agents_standing_cap:
                    snippet = snippet[:agents_standing_cap] + "\n… [truncated — read_file('AGENTS.md')]"
                parts.append(f"{label}\n{snippet}\n")
            else:
                parts.append(f"{label}\n<file does not exist yet>\n")
            continue
        if upper == "SESSION-STATE.md":
            from persistence import _personality_file_block

            parts.append(_personality_file_block(ws, base))
            continue
        from persistence import _personality_file_block

        parts.append(_personality_file_block(ws, base))

    suffix = (
        "\n… Profile summary only — use read_file on PERSONA.md or AGENTS.md "
        "when you need the full soul or standing orders.\n"
    )
    return "".join(parts) + suffix
