"""
Raíz del proyecto y serialización al formato ``.area``.

Un ``.area`` es un archivo ZIP que contiene:

::

    project.json     — metadata del proyecto y modelo serializado
    assets/          — recursos (íconos custom, capturas, fotografías)

El formato JSON usa el modelo declarado por el resto de ``arelec.core`` y
está versionado por el campo ``schema_version``. Los .area producidos por
una versión del software pueden ser leídos por versiones posteriores
(migraciones aditivas), nunca por anteriores.

En Sprint 0 el modelo es mínimo (solo metadata) — los sub-modelos (ship,
decks, appliances, panels, cables…) se agregan en sprints siguientes.
"""

from __future__ import annotations

import json
import zipfile
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

SCHEMA_VERSION = 1


@dataclass
class ProjectMetadata:
    """Datos administrativos del proyecto."""

    name: str = "Nuevo proyecto"
    author: str = ""
    company: str = ""
    notes: str = ""
    schema_version: int = SCHEMA_VERSION
    created_at: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )
    modified_at: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )


@dataclass
class Project:
    """
    Raíz del modelo de datos. Sprint 0 contiene solo metadata; los sub-modelos
    (ship, decks, appliances, panels, cable_runs, batteries, etc.) se acoplan
    a esta clase en sprints siguientes.

    La instancia se serializa íntegra como ``project.json`` dentro del ZIP
    ``.area``.
    """

    metadata: ProjectMetadata = field(default_factory=ProjectMetadata)
    # Sub-modelos futuros se irán agregando aquí:
    # ship: Ship | None = None
    # decks: list[Deck] = field(default_factory=list)
    # appliances: list[Appliance] = field(default_factory=list)
    # batteries: list[BatteryBank] = field(default_factory=list)
    # panels: list[Panel] = field(default_factory=list)
    # cable_runs: list[CableRun] = field(default_factory=list)

    # ── Serialization ───────────────────────────────────────────────────────
    def to_json(self) -> dict[str, Any]:
        """Convertir a dict serializable (JSON-safe)."""
        return asdict(self)

    @classmethod
    def from_json(cls, data: dict[str, Any]) -> "Project":
        """Reconstruir desde dict cargado de ``project.json``."""
        meta_dict = data.get("metadata", {})
        return cls(metadata=ProjectMetadata(**meta_dict))

    # ── .area I/O ───────────────────────────────────────────────────────────
    def save(self, path: str | Path) -> None:
        """
        Persistir el proyecto a ``path`` (extensión ``.area`` sugerida).

        Toca ``metadata.modified_at`` con la hora UTC actual antes de
        serializar.
        """
        path = Path(path)
        self.metadata.modified_at = datetime.now(timezone.utc).isoformat()
        payload = json.dumps(self.to_json(), indent=2, ensure_ascii=False)
        # ``ZIP_DEFLATED`` para que los .area no crezcan absurdo en proyectos
        # grandes — el contenido es texto y comprime ~10×.
        with zipfile.ZipFile(path, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
            zf.writestr("project.json", payload)

    @classmethod
    def load(cls, path: str | Path) -> "Project":
        """
        Cargar un proyecto desde ``path``. Lanza:

        - ``FileNotFoundError`` si el archivo no existe
        - ``zipfile.BadZipFile`` si el archivo no es ZIP válido
        - ``ValueError`` si ``schema_version`` es mayor que ``SCHEMA_VERSION``
          (el .area fue creado por una versión más nueva del software)
        - ``KeyError`` si falta ``project.json`` en el ZIP
        """
        path = Path(path)
        with zipfile.ZipFile(path, mode="r") as zf:
            with zf.open("project.json") as f:
                data = json.loads(f.read().decode("utf-8"))

        version = data.get("metadata", {}).get("schema_version", 1)
        if version > SCHEMA_VERSION:
            raise ValueError(
                f"El proyecto requiere schema_version={version}, "
                f"este software soporta hasta {SCHEMA_VERSION}. "
                f"Actualiza AR-ElecArrangement."
            )
        return cls.from_json(data)
