"""VP pack stage state machine — forward-only transitions."""

from __future__ import annotations

from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any

from niannian.virtual_persona.pack import VALID_STAGES, read_pack, validate_stage, write_pack

if TYPE_CHECKING:
    from niannian.virtual_persona.store import VirtualPersonaStore


class StageTransitionError(ValueError):
    """Illegal forward-only stage transition."""


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _next_stage(current: str) -> str | None:
    try:
        idx = VALID_STAGES.index(current)
    except ValueError as exc:
        raise StageTransitionError(f"unknown current stage: {current!r}") from exc
    if idx + 1 >= len(VALID_STAGES):
        return None
    return VALID_STAGES[idx + 1]


def get_stage(store: VirtualPersonaStore, vp_id: str) -> str:
    pack = read_pack(store, vp_id)
    return str(pack["stage"])


def _touch_meta_updated_at(store: VirtualPersonaStore, vp_id: str) -> None:
    import yaml

    dest = store.vp_dir(vp_id)
    meta_path = dest / "meta.yaml"
    if not meta_path.is_file():
        return
    with meta_path.open(encoding="utf-8") as f:
        meta = yaml.safe_load(f) or {}
    if not isinstance(meta, dict):
        return
    meta["updated_at"] = _now_iso()
    with meta_path.open("w", encoding="utf-8") as f:
        yaml.safe_dump(meta, f, allow_unicode=True, sort_keys=False)


def transition_stage(store: VirtualPersonaStore, vp_id: str, target: str) -> dict[str, Any]:
    target_stage = validate_stage(target)
    current = get_stage(store, vp_id)

    if target_stage == current:
        pack = read_pack(store, vp_id)
        return {
            "vp_id": vp_id,
            "previous_stage": current,
            "stage": current,
            "pack": pack,
        }

    expected = _next_stage(current)
    if expected != target_stage:
        hint = f" (expected {expected})" if expected else " (terminal stage)"
        raise StageTransitionError(f"invalid stage transition: {current} -> {target_stage}{hint}")

    pack = read_pack(store, vp_id)
    pack["stage"] = target_stage
    pack["updated_at"] = _now_iso()
    write_pack(store, vp_id, pack)
    _touch_meta_updated_at(store, vp_id)

    return {
        "vp_id": vp_id,
        "previous_stage": current,
        "stage": target_stage,
        "pack": pack,
    }
