"""
world_data.py — World metadata model and persistence for Powernet.

Each world is saved as a compressed folder inside  <project_root>/saves/.
The folder is named  <safe_name>_<world_id>/  and contains world.dat
(gzip-compressed JSON).

list_worlds()   — scan saves/ and return all worlds sorted by last_played
"""

from __future__ import annotations

import gzip
import json
import os
import re
import shutil
import sys
import time
import uuid
from dataclasses import asdict, dataclass, field, fields
from pathlib import Path


def _app_data_dir() -> Path:
    """
    Return the platform-appropriate application data directory.

    Windows  : %APPDATA%\\Powernet
    macOS    : ~/Library/Application Support/Powernet
    Linux    : $XDG_DATA_HOME/Powernet  (default ~/.local/share/Powernet)
    """
    if sys.platform == "win32":
        base = Path(os.environ.get("APPDATA", Path.home()))
    elif sys.platform == "darwin":
        base = Path.home() / "Library" / "Application Support"
    else:
        xdg = os.environ.get("XDG_DATA_HOME", "")
        base = Path(xdg) if xdg else Path.home() / ".local" / "share"
    return base / "Powernet"


SAVES_DIR = _app_data_dir() / "saves"


def _safe_name(name: str) -> str:
    """Convert a world name to a filesystem-safe component (max 30 chars)."""
    safe = re.sub(r"[^\w\-]", "_", name.strip())[:30]
    return safe or "world"


# ---------------------------------------------------------------------------
# Icon registry
# ---------------------------------------------------------------------------
# Each entry maps a human-readable id to a texture path relative to
# assets/textures/other/.

TEX_BASE = Path(__file__).parent.parent / "assets" / "textures" / "other"

WORLD_ICONS: list[dict] = [
    {"id": 0,  "label": "Solar Panel",    "path": "gen/solar-panels.png"},
    {"id": 1,  "label": "Biogas",         "path": "gen/bio-1.png"},
    {"id": 2,  "label": "Gas Generator",  "path": "gen/gas-gen.png"},
    {"id": 3,  "label": "Geothermal",     "path": "gen/geothermie-1.png"},
    {"id": 4,  "label": "Solar Thermal",  "path": "gen/soalrthermie.png"},
    {"id": 5,  "label": "Fusion",         "path": "gen/fusion.png"},
    {"id": 6,  "label": "Bungalow",       "path": "build/bungalow.png"},
    {"id": 7,  "label": "House",          "path": "build/house.png"},
    {"id": 8,  "label": "Apartment",      "path": "build/multifamily-house.png"},
    {"id": 9,  "label": "Small Factory",  "path": "build/small-factory.png"},
    {"id": 10, "label": "Factory",        "path": "build/normal-factory.png"},
    {"id": 11, "label": "Big Factory",    "path": "build/big-factory-1.png"},
    {"id": 12, "label": "Warehouse",      "path": "build/industriehalle.png"},
    {"id": 13, "label": "Research",       "path": "misc/small-forschung.png"},
    {"id": 14, "label": "Lab",            "path": "misc/forschung-on.png"},
    {"id": 15, "label": "Big Lab",        "path": "misc/big-forschung.png"},
]


# ---------------------------------------------------------------------------
# WorldMeta dataclass
# ---------------------------------------------------------------------------


@dataclass
class WorldMeta:
    name: str
    difficulty: str                # "easy" | "hard"
    icon_id: int = 0               # index into WORLD_ICONS
    world_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
    created: float = field(default_factory=time.time)
    last_played: float = 0.0      # epoch; 0 = never played
    play_time: float = 0.0        # cumulative seconds
    file_size: int = 256           # bytes — compressed world.dat size
    save_name: str = ""            # stable folder component — set on first save
    game_data: dict = field(default_factory=dict)  # serialised GameState
    seed: int = 0                  # world generation seed

    # ---- computed path helpers ----

    @property
    def world_dir(self) -> Path:
        """Folder that contains world.dat for this world.

        Uses ``save_name`` (set on first save, never changed) so that
        renaming the world does not move the folder.
        """
        folder = self.save_name if self.save_name else _safe_name(self.name)
        return SAVES_DIR / f"{folder}_{self.world_id}"

    # ---- human-readable helpers ----

    @property
    def last_played_label(self) -> str:
        if self.last_played == 0.0:
            return "Never played"
        delta = time.time() - self.last_played
        if delta < 60:
            return "Just now"
        if delta < 3_600:
            m = int(delta / 60)
            return f"{m} min{'s' if m != 1 else ''} ago"
        if delta < 86_400:
            h = int(delta / 3_600)
            return f"{h} hour{'s' if h != 1 else ''} ago"
        if delta < 86_400 * 7:
            d = int(delta / 86_400)
            return f"{d} day{'s' if d != 1 else ''} ago"
        from datetime import datetime
        return datetime.fromtimestamp(self.last_played).strftime("%d %b %Y")

    @property
    def size_label(self) -> str:
        kb = self.file_size / 1024
        return f"{kb:.1f} KB" if kb < 1024 else f"{kb / 1024:.1f} MB"

    @property
    def difficulty_label(self) -> str:
        return self.difficulty.upper()

    @property
    def icon_info(self) -> dict:
        for ic in WORLD_ICONS:
            if ic["id"] == self.icon_id:
                return ic
        return WORLD_ICONS[0]

    # ---- persistence ----

    def save(self) -> None:
        # Lock the folder name on first save
        if not self.save_name:
            self.save_name = _safe_name(self.name)
        d = self.world_dir
        d.mkdir(parents=True, exist_ok=True)
        path = d / "world.dat"
        payload = json.dumps(asdict(self)).encode()
        with gzip.open(path, "wb") as fh:
            fh.write(payload)
        self.file_size = max(256, path.stat().st_size)
        # Re-write with updated file_size
        payload = json.dumps(asdict(self)).encode()
        with gzip.open(path, "wb") as fh:
            fh.write(payload)

    @classmethod
    def load(cls, world_id: str) -> "WorldMeta":
        if SAVES_DIR.exists():
            for folder in SAVES_DIR.iterdir():
                if folder.is_dir() and folder.name.endswith(f"_{world_id}"):
                    path = folder / "world.dat"
                    if path.exists():
                        with gzip.open(path, "rb") as fh:
                            data = json.loads(fh.read())
                        valid_keys = {f.name for f in fields(cls)}
                        filtered = {k: v for k, v in data.items() if k in valid_keys}
                        return cls(**filtered)
        raise FileNotFoundError(f"World {world_id} not found")

    def delete(self) -> None:
        if self.world_dir.exists():
            shutil.rmtree(self.world_dir)


# ---------------------------------------------------------------------------
# Collection helpers
# ---------------------------------------------------------------------------


def list_worlds() -> list[WorldMeta]:
    """
    Scan the saves directory and return all valid worlds.

    Sorted by ``last_played`` descending (most recent first).
    Worlds that have never been played appear at the bottom.
    """
    if not SAVES_DIR.exists():
        return []
    worlds: list[WorldMeta] = []
    for folder in SAVES_DIR.iterdir():
        if not folder.is_dir():
            continue
        path = folder / "world.dat"
        if not path.exists():
            continue
        try:
            with gzip.open(path, "rb") as fh:
                data = json.loads(fh.read())
            valid_keys = {f.name for f in fields(WorldMeta)}
            filtered = {k: v for k, v in data.items() if k in valid_keys}
            worlds.append(WorldMeta(**filtered))
        except Exception:
            continue
    worlds.sort(key=lambda w: w.last_played, reverse=True)
    return worlds
