"""Save and restore game state to/from JSON files."""

from __future__ import annotations

import copy
import json
import os
from pathlib import Path
from typing import Any

from h2g2.engine.game_object import Flag
from h2g2.engine.state import GameState

SAVE_DIR = Path.home() / ".h2g2_saves"
SAVE_EXTENSION = ".json"


def _ensure_save_dir() -> Path:
    SAVE_DIR.mkdir(parents=True, exist_ok=True)
    return SAVE_DIR


def list_saves() -> list[str]:
    """Return sorted list of save-file names (without extension)."""
    if not SAVE_DIR.exists():
        return []
    names = [
        p.stem for p in sorted(SAVE_DIR.iterdir())
        if p.suffix == SAVE_EXTENSION and p.is_file()
    ]
    return names


# -- Serialization -----------------------------------------------------------

def _obj_id(obj: Any) -> str | None:
    if obj is None:
        return None
    return obj.id


def _serialize_state(state: GameState) -> dict[str, Any]:
    """Snapshot all mutable game state into a JSON-safe dict."""
    # Object flags + containment for every object in the world
    objects: dict[str, Any] = {}
    for obj_id, obj in state.world.objects.items():
        objects[obj_id] = {
            "flags": [f.name for f in obj.flags],
            "parent": _obj_id(obj.parent),
        }

    # Clock entries (matched by name)
    clocks: list[dict[str, Any]] = []
    for entry in state.clock.entries:
        clocks.append({
            "name": entry.name,
            "tick": entry.tick,
            "enabled": entry.enabled,
        })

    return {
        "version": 1,
        "here": _obj_id(state.here),
        "protagonist": _obj_id(state.protagonist),
        "winner": _obj_id(state.winner),
        "identity_flag": _obj_id(state.identity_flag),
        "score": state.score,
        "moves": state.moves,
        "lit": state.lit,
        "lying_down": state.lying_down,
        "verbosity": state.verbosity,
        "dreaming": state.dreaming,
        "running": state.running,
        "l_prsa": state.l_prsa,
        "l_prso": _obj_id(state.l_prso),
        "l_prsi": _obj_id(state.l_prsi),
        "flags": {k: copy.deepcopy(v) for k, v in state.flags.items()
                  if not k.startswith("_")},
        "objects": objects,
        "clocks": clocks,
    }


def _restore_state(state: GameState, data: dict[str, Any]) -> None:
    """Apply a serialized snapshot back onto the live game state."""
    world = state.world

    def resolve(obj_id: str | None) -> Any:
        if obj_id is None:
            return None
        return world.objects.get(obj_id)

    def resolve_room(obj_id: str | None) -> Any:
        if obj_id is None:
            return None
        return world.rooms.get(obj_id)

    # Scalar state
    state.here = resolve_room(data["here"])
    state.protagonist = resolve(data["protagonist"])
    state.winner = resolve(data["winner"])
    state.identity_flag = resolve(data.get("identity_flag"))
    state.score = data["score"]
    state.moves = data["moves"]
    state.lit = data["lit"]
    state.lying_down = data["lying_down"]
    state.verbosity = data["verbosity"]
    state.dreaming = data["dreaming"]
    state.running = data["running"]
    state.l_prsa = data["l_prsa"]
    state.l_prso = resolve(data["l_prso"])
    state.l_prsi = resolve(data["l_prsi"])
    # Preserve private flags (e.g. _game_loop) across restore
    private = {k: v for k, v in state.flags.items() if k.startswith("_")}
    state.flags = data["flags"]
    state.flags.update(private)

    # Restore object flags and rebuild containment tree
    # First pass: set flags and detach all children
    for obj in world.objects.values():
        obj.parent = None
        obj.children.clear()

    # Second pass: restore flags and re-attach parents
    for obj_id, obj_data in data["objects"].items():
        obj = world.objects.get(obj_id)
        if obj is None:
            continue
        obj.flags = {Flag[name] for name in obj_data["flags"]}
        parent_id = obj_data["parent"]
        if parent_id is not None:
            parent = world.objects.get(parent_id)
            if parent is not None:
                obj.parent = parent
                parent.children.append(obj)

    # Restore clock state (match by name)
    clock_data = {c["name"]: c for c in data.get("clocks", []) if c["name"]}
    for entry in state.clock.entries:
        saved = clock_data.get(entry.name)
        if saved is not None:
            entry.tick = saved["tick"]
            entry.enabled = saved["enabled"]


# -- File I/O ----------------------------------------------------------------

def save_game(state: GameState, name: str) -> str:
    """Save game to file. Returns the full path written."""
    _ensure_save_dir()
    path = SAVE_DIR / (name + SAVE_EXTENSION)
    data = _serialize_state(state)
    path.write_text(json.dumps(data, indent=2), encoding="utf-8")
    return str(path)


def load_game(state: GameState, name: str) -> None:
    """Load game from file. Raises FileNotFoundError if missing."""
    path = SAVE_DIR / (name + SAVE_EXTENSION)
    data = json.loads(path.read_text(encoding="utf-8"))
    _restore_state(state, data)
