"""Local revision chain for `osmosis expand` artifacts."""

from __future__ import annotations

import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional


def _utc_now() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def expand_revision_home() -> Path:
    env = str(os.environ.get("OSMOSIS_EXPAND_HOME", "")).strip()
    if env:
        return Path(env)
    return Path(".model_osmosis") / "expand"


def revision_state_path() -> Path:
    return expand_revision_home() / "revisions.json"


def _default_state() -> Dict[str, Any]:
    return {
        "schema_version": 1,
        "updated_at": "",
        "active_revision_id": "",
        "revisions": [],
    }


def _normalize_revision(item: Dict[str, Any]) -> Dict[str, Any]:
    return {
        "revision_id": str(item.get("revision_id", "") or ""),
        "parent_revision_id": str(item.get("parent_revision_id", "") or ""),
        "name": str(item.get("name", "") or ""),
        "artifact_path": str(item.get("artifact_path", "") or ""),
        "accepted": bool(item.get("accepted", False)),
        "baseline_metric": float(item.get("baseline_metric", 0.0) or 0.0),
        "post_expand_metric": float(item.get("post_expand_metric", 0.0) or 0.0),
        "quality_delta": float(item.get("quality_delta", 0.0) or 0.0),
        "rollback_pointer": str(item.get("rollback_pointer", "") or ""),
        "created_at": str(item.get("created_at", "") or ""),
        "report": dict(item.get("report", {}) or {}),
    }


def _normalize_state(state: Dict[str, Any]) -> Dict[str, Any]:
    revisions_raw = state.get("revisions", [])
    revisions: List[Dict[str, Any]] = []
    if isinstance(revisions_raw, list):
        for item in revisions_raw:
            if isinstance(item, dict):
                normalized = _normalize_revision(item)
                if normalized["revision_id"]:
                    revisions.append(normalized)
    revisions = sorted(revisions, key=lambda item: item["revision_id"])
    out = _default_state()
    out["schema_version"] = int(state.get("schema_version", 1) or 1)
    out["updated_at"] = str(state.get("updated_at", "") or "")
    out["active_revision_id"] = str(state.get("active_revision_id", "") or "")
    out["revisions"] = revisions
    return out


def load_revision_state() -> Dict[str, Any]:
    path = revision_state_path()
    if not path.exists():
        return _default_state()
    try:
        parsed = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return _default_state()
    if not isinstance(parsed, dict):
        return _default_state()
    return _normalize_state(parsed)


def save_revision_state(state: Dict[str, Any]) -> Path:
    path = revision_state_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    normalized = _normalize_state(state)
    normalized["updated_at"] = _utc_now()
    path.write_text(json.dumps(normalized, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    return path


def _safe_name(name: str) -> str:
    cleaned = "".join(ch if ch.isalnum() or ch in {"-", "_"} else "_" for ch in str(name))
    return cleaned.strip("_") or "model"


def _next_revision_number(state: Dict[str, Any], name: str) -> int:
    prefix = f"{_safe_name(name)}.r"
    numbers: List[int] = []
    for item in state.get("revisions", []):
        rid = str(item.get("revision_id", ""))
        if not rid.startswith(prefix):
            continue
        token = rid.replace(prefix, "", 1)
        if token.isdigit():
            numbers.append(int(token))
    return (max(numbers) + 1) if numbers else 1


def next_revision_id(name: str) -> str:
    state = load_revision_state()
    seq = _next_revision_number(state, name)
    return f"{_safe_name(name)}.r{seq:04d}"


def default_revision_output_path(name: str, revision_id: str) -> Path:
    return Path("models") / f"{revision_id}.osmoimg"


def list_revisions(*, name: Optional[str] = None) -> List[Dict[str, Any]]:
    state = load_revision_state()
    revisions = list(state.get("revisions", []))
    if name:
        safe = _safe_name(name)
        revisions = [item for item in revisions if str(item.get("name", "")) == safe]
    return sorted((_normalize_revision(item) for item in revisions), key=lambda item: item["revision_id"])


def active_revision(*, name: Optional[str] = None) -> Optional[Dict[str, Any]]:
    state = load_revision_state()
    active_id = str(state.get("active_revision_id", "") or "")
    revisions = list_revisions(name=name)
    if not revisions:
        return None
    if active_id:
        for item in revisions:
            if item["revision_id"] == active_id:
                return item
    return revisions[-1]


def append_revision(
    *,
    name: str,
    artifact_path: str,
    accepted: bool,
    baseline_metric: float,
    post_expand_metric: float,
    quality_delta: float,
    report: Dict[str, Any],
) -> Dict[str, Any]:
    state = load_revision_state()
    safe_name = _safe_name(name)
    revision_id = next_revision_id(safe_name)
    parent = active_revision(name=safe_name)
    parent_id = str(parent.get("revision_id", "") if isinstance(parent, dict) else "")
    entry = _normalize_revision(
        {
            "revision_id": revision_id,
            "parent_revision_id": parent_id,
            "name": safe_name,
            "artifact_path": str(artifact_path),
            "accepted": bool(accepted),
            "baseline_metric": float(baseline_metric),
            "post_expand_metric": float(post_expand_metric),
            "quality_delta": float(quality_delta),
            "rollback_pointer": parent_id if not accepted else "",
            "created_at": _utc_now(),
            "report": dict(report or {}),
        }
    )
    revisions = list(state.get("revisions", []))
    revisions.append(entry)
    revisions = sorted((_normalize_revision(item) for item in revisions), key=lambda item: item["revision_id"])
    state["revisions"] = revisions
    if entry["accepted"]:
        state["active_revision_id"] = entry["revision_id"]
    elif parent_id:
        state["active_revision_id"] = parent_id
    save_revision_state(state)
    return entry


def rollback_revision(to_revision_id: str) -> Dict[str, Any]:
    target = str(to_revision_id or "").strip()
    if not target:
        raise ValueError("Revision id is required")
    state = load_revision_state()
    revisions = list(state.get("revisions", []))
    match = None
    for item in revisions:
        rid = str(item.get("revision_id", ""))
        if rid == target:
            match = _normalize_revision(item)
            break
    if match is None:
        raise RuntimeError(f"Unknown revision id: {target}")
    state["active_revision_id"] = target
    save_revision_state(state)
    return match
