"""Seed templates — ship with the app, installed to user's data root on init.

Non-user defaults for config/, state/, saved_prompts/ that get copied when
the user runs vastian init or migrates to a new data root.

Data root is resolved via VASTIAN_DATA_ROOT or config/local/data_root.txt
(e.g. C:/Users/Owner/Documents/myvastian-user-data).

Approach C additions:
- ``heal_missing_docs()`` — self-heal missing agent docs on hydration/startup
- ``fixup_state()`` — add missing JSON fields with defaults on hydration/startup
"""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any

import yaml

logger = logging.getLogger(__name__)


def _seed_dir() -> Path:
    """Return the seed template directory (works for dev and installed package)."""
    return Path(__file__).resolve().parent


def get_seed_path(*parts: str) -> Path:
    """Return path to a seed file or directory. parts e.g. ('config', 'ui-preferences.yaml')."""
    return _seed_dir().joinpath(*parts)


def install_config_seed(config_dir: Path) -> None:
    """Copy default config files to config_dir recursively. Skips if target already exists."""
    seed = _seed_dir()
    config_seed = seed / "config"
    if not config_seed.exists():
        return
    config_dir.mkdir(parents=True, exist_ok=True)
    _copy_tree(config_seed, config_dir)


def _copy_tree(src: Path, dst: Path) -> None:
    """Recursively copy files from *src* to *dst*, skipping existing targets."""
    dst.mkdir(parents=True, exist_ok=True)
    for item in sorted(src.iterdir()):
        target = dst / item.name
        if item.is_dir():
            _copy_tree(item, target)
        elif item.is_file() and item.name != ".gitkeep" and not target.exists():
            target.write_text(item.read_text(encoding="utf-8"), encoding="utf-8")


def install_saved_prompts_seed(saved_prompts_dir: Path) -> None:
    """Create saved_prompts structure with inbox README. Idempotent."""
    for sub in ("inbox", "intake", "complete"):
        (saved_prompts_dir / sub).mkdir(parents=True, exist_ok=True)

    readme_src = _seed_dir() / "saved_prompts" / "inbox" / "README.md"
    readme_dst = saved_prompts_dir / "inbox" / "README.md"
    if readme_src.exists() and not readme_dst.exists():
        readme_dst.write_text(readme_src.read_text(encoding="utf-8"), encoding="utf-8")

    intro_src = _seed_dir() / "saved_prompts" / "intro.example.txt"
    intro_dst = saved_prompts_dir / "intro.example.txt"
    if intro_src.exists() and not intro_dst.exists():
        intro_dst.write_text(intro_src.read_text(encoding="utf-8"), encoding="utf-8")


def install_state_seed(state_dir: Path) -> None:
    """Create full state directory suite with empty JSON files and subdirs."""
    state_dir.mkdir(parents=True, exist_ok=True)

    # Empty list/dict JSON files (only if missing)
    empty_list = "[]\n"
    empty_obj = "{}\n"
    users_json = '{"users":[{"id":"default","name":"User"}]}\n'
    state_files: list[tuple[str, str]] = [
        ("kanban.json", empty_list),
        ("sessions.json", empty_list),
        ("delegations.json", empty_list),
        ("delegation-reports.json", empty_list),
        ("council-sessions.json", empty_list),
        ("knowledge.json", empty_obj),
        ("tasks.json", empty_list),
        ("pending-cards.json", empty_list),
        ("session-journal.json", empty_list),
        ("config-proposals-audit.json", empty_list),
        ("config-changelog.json", empty_list),
        ("pending-config-cards.json", empty_list),
        ("sprints.json", empty_list),
        ("users.json", users_json),
        ("conversations.json", empty_list),  # Cloud user conversation continuity
    ]
    for name, default in state_files:
        p = state_dir / name
        if not p.exists():
            p.write_text(default, encoding="utf-8")

    # Subdirs with .gitkeep
    subdirs = [
        "relay/outbox/config",
        "memory-inbox",
        "giga-outbox/plans",
        "giga-outbox/neurons",
        "giga-outbox/sent",
        "content-bridge/transcript",
        "content-bridge/meeting",
        "content-bridge/manual",
        "prompt-baselines",
    ]
    for sub in subdirs:
        d = state_dir / sub
        d.mkdir(parents=True, exist_ok=True)
        keep = d / ".gitkeep"
        if not keep.exists():
            keep.write_text("", encoding="utf-8")


def install_data_seed(data_dir: Path) -> None:
    """Create data/ structure (chromadb/, placeholder for vastian.db, tasks.db).

    SQLite and ChromaDB initialize their files on first use; this ensures dirs exist.
    """
    data_dir.mkdir(parents=True, exist_ok=True)
    (data_dir / "chromadb").mkdir(parents=True, exist_ok=True)


# ── Approach C: Doc Self-Healing ─────────────────────────────────


def _load_agent_owned_docs(agents_dir: Path) -> dict[str, list[str]]:
    """Read all agent YAMLs and return {agent_id: [doc_name, ...]}."""
    result: dict[str, list[str]] = {}
    if not agents_dir.exists():
        return result
    for f in sorted(agents_dir.glob("*.yaml")):
        try:
            data = yaml.safe_load(f.read_text(encoding="utf-8"))
            if not isinstance(data, dict):
                continue
            agent_id = data.get("agent_id", f.stem)
            owned = data.get("owned_documents", [])
            if isinstance(owned, list) and owned:
                result[agent_id] = [str(d) for d in owned]
        except Exception:
            logger.warning("Failed to read agent YAML: %s", f, exc_info=True)
    return result


def _make_placeholder_doc(doc_name: str, agent_id: str) -> str:
    """Generate placeholder Markdown for a missing agent doc."""
    title = doc_name.replace(".md", "").replace("-", " ").title()
    return (
        f"# {title}\n\n"
        f"*Owned by: {agent_id}*\n\n"
        f"This document was auto-created as a placeholder. "
        f"It will be populated during onboarding or when {agent_id} "
        f"first writes to it.\n"
    )


def heal_missing_docs(
    agents_dir: Path,
    docs_dir: Path,
    *,
    dry_run: bool = False,
) -> list[str]:
    """Create missing agent docs from placeholders.  Existing docs are never touched.

    Called on hydration (cloud users) or startup (desktop users).

    Parameters
    ----------
    agents_dir:
        Path to agents/*.yaml (the app's agent definitions — NOT per-user).
    docs_dir:
        Path to the user's docs/ directory.
    dry_run:
        If True, return the list of docs that *would* be created without writing.

    Returns
    -------
    list[str]
        Paths (relative to docs_dir) of docs that were created (or would be).
    """
    agent_docs = _load_agent_owned_docs(agents_dir)
    created: list[str] = []

    for agent_id, doc_names in agent_docs.items():
        agent_doc_dir = docs_dir / agent_id
        for doc_name in doc_names:
            # Ensure .md extension
            if not doc_name.endswith(".md"):
                doc_name = f"{doc_name}.md"
            target = agent_doc_dir / doc_name
            if target.exists():
                continue
            rel = f"{agent_id}/{doc_name}"
            if dry_run:
                created.append(rel)
                continue
            try:
                target.parent.mkdir(parents=True, exist_ok=True)
                content = _make_placeholder_doc(doc_name, agent_id)
                target.write_text(content, encoding="utf-8")
                created.append(rel)
                logger.info("Created missing doc: %s", rel)
            except Exception:
                logger.warning("Failed to create doc: %s", rel, exc_info=True)

    if created and not dry_run:
        logger.info("Healed %d missing doc(s)", len(created))
    return created


# ── Approach C: State Schema Fixups ──────────────────────────────

# Expected state files and their default structures.
# For list files: ensure file exists with [].
# For dict files: ensure required top-level keys exist with defaults.
_STATE_SCHEMA: dict[str, Any] = {
    "kanban.json": [],
    "sessions.json": [],
    "delegations.json": [],
    "delegation-reports.json": [],
    "council-sessions.json": [],
    "knowledge.json": {},
    "tasks.json": [],
    "pending-cards.json": [],
    "session-journal.json": [],
    "config-proposals-audit.json": [],
    "config-changelog.json": [],
    "pending-config-cards.json": [],
    "sprints.json": [],
    "users.json": {"users": [{"id": "default", "name": "User"}]},
    "conversations.json": [],
}


def fixup_state(state_dir: Path) -> list[str]:
    """Ensure state JSON files exist and have required schema.

    - Missing files are created with defaults.
    - Dict files get missing top-level keys added (existing keys untouched).
    - List files are left as-is if they exist (even if empty).

    Returns list of files that were created or modified.
    """
    changed: list[str] = []
    state_dir.mkdir(parents=True, exist_ok=True)

    for filename, default in _STATE_SCHEMA.items():
        path = state_dir / filename
        if not path.exists():
            # Create with default
            path.write_text(
                json.dumps(default, indent=2, ensure_ascii=False) + "\n",
                encoding="utf-8",
            )
            changed.append(filename)
            continue

        # For dict schemas, add missing top-level keys
        if isinstance(default, dict) and default:
            try:
                raw = path.read_text(encoding="utf-8").strip()
                data = json.loads(raw) if raw else {}
                if not isinstance(data, dict):
                    continue
                added = False
                for key, val in default.items():
                    if key not in data:
                        data[key] = val
                        added = True
                if added:
                    path.write_text(
                        json.dumps(data, indent=2, ensure_ascii=False) + "\n",
                        encoding="utf-8",
                    )
                    changed.append(filename)
            except Exception:
                logger.warning("Failed to fixup state file: %s", filename, exc_info=True)

    return changed


def hydrate_user_data(
    agents_dir: Path,
    data_root: Path,
) -> dict[str, Any]:
    """Run all Approach C hydration steps for a user's data root.

    This is the single entry point called during cloud hydration or desktop startup.
    Runs: config seed install, doc self-heal, state fixup, pending migrations.

    Parameters
    ----------
    agents_dir:
        Path to agents/*.yaml (app's agent definitions).
    data_root:
        Path to the user's data root (docs/, state/, config/, etc.).

    Returns
    -------
    dict with keys: configs_installed, docs_healed, state_fixed, migrations_applied
    """
    from vastian.seed.migrations import run_pending

    config_dir = data_root / "config"
    docs_dir = data_root / "docs"
    state_dir = data_root / "state"

    # 1. Config seed install (copy-if-missing, existing behavior)
    install_config_seed(config_dir)

    # 2. State seed install (create dirs and empty JSON files)
    install_state_seed(state_dir)

    # 3. Doc self-heal
    healed = heal_missing_docs(agents_dir, docs_dir)

    # 4. State fixups
    fixed = fixup_state(state_dir)

    # 5. Versioned migrations
    migrated = run_pending(state_dir, data_root)

    return {
        "docs_healed": healed,
        "state_fixed": fixed,
        "migrations_applied": migrated,
    }
