"""Seed versioning and migration registry — Approach C escape hatch.

Tracks which version of the seed data a user has been migrated to via
``state/seed-version.json`` in the user's data root.  Versioned migrations
handle non-declarative changes (renames, data transforms, one-time fixes)
that can't be done with config layering, doc self-heal, or state fixups.

Usage::

    from vastian.seed.migrations import run_pending

    # On hydration (cloud) or startup (desktop):
    run_pending(state_dir=Path("/tmp/user123/state"), data_root=Path("/tmp/user123"))

The same code path runs on desktop startup AND cloud user hydration.
"""

from __future__ import annotations

import json
import logging
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

# ── Current seed version ────────────────────────────────────────
# Bump this when adding a new migration.
SEED_VERSION = "1.0.0"


# ── Migration registry ──────────────────────────────────────────
# Ordered list of (version, description, callable).
# Each callable receives (data_root: Path) and should be idempotent.
_MIGRATIONS: list[tuple[str, str, Any]] = [
    # ("1.1.0", "Rename foo.json to bar.json", _migrate_1_1_0),
    # Add new migrations here.  Each must be idempotent.
]


# ── Public API ──────────────────────────────────────────────────


def get_seed_version(state_dir: Path) -> dict[str, Any]:
    """Read ``state/seed-version.json`` and return parsed contents.

    Returns a default dict if the file doesn't exist.
    """
    path = state_dir / "seed-version.json"
    if not path.exists():
        return {
            "version": "0.0.0",
            "migrations_applied": [],
            "last_run": None,
        }
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(data, dict):
            return {"version": "0.0.0", "migrations_applied": [], "last_run": None}
        return data
    except Exception:
        logger.warning("Failed to read seed-version.json", exc_info=True)
        return {"version": "0.0.0", "migrations_applied": [], "last_run": None}


def _write_seed_version(state_dir: Path, data: dict[str, Any]) -> None:
    """Write ``state/seed-version.json``."""
    path = state_dir / "seed-version.json"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(data, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def run_pending(state_dir: Path, data_root: Path) -> list[str]:
    """Run any migrations not yet applied for this user.

    Parameters
    ----------
    state_dir:
        Path to the user's state directory (contains seed-version.json).
    data_root:
        Path to the user's full data root (docs/, state/, config/).

    Returns
    -------
    list[str]
        Versions of migrations that were applied in this run.
    """
    info = get_seed_version(state_dir)
    applied = set(info.get("migrations_applied", []))
    newly_applied: list[str] = []

    for version, description, migrate_fn in _MIGRATIONS:
        if version in applied:
            continue
        try:
            logger.info("Running migration %s: %s", version, description)
            migrate_fn(data_root)
            applied.add(version)
            newly_applied.append(version)
        except Exception:
            logger.error("Migration %s failed", version, exc_info=True)
            # Stop on failure — don't skip migrations
            break

    # Always update version to current, even if no migrations ran
    # (this tracks "this user's data is compatible with SEED_VERSION")
    info["version"] = SEED_VERSION
    info["migrations_applied"] = sorted(applied)
    info["last_run"] = datetime.now(tz=UTC).isoformat()
    _write_seed_version(state_dir, info)

    if newly_applied:
        logger.info("Applied %d migration(s): %s", len(newly_applied), newly_applied)

    return newly_applied


def needs_migration(state_dir: Path) -> bool:
    """Return True if there are pending migrations for this user."""
    info = get_seed_version(state_dir)
    applied = set(info.get("migrations_applied", []))
    return any(v not in applied for v, _, _ in _MIGRATIONS)
