"""Typed, frozen models for the declarative provisioning surface.

A :class:`ServiceSpec` is the *desired state* of one self-hosted service
(devpi, a CI agent, Vault, …) expressed as a VM + its cloud-init. ``plan`` diffs
it against the live cloud and yields a :class:`ServicePlan` (CREATE / NOOP /
REPLACE); ``apply`` realises that plan and returns an :class:`ApplyResult`.
"""

from __future__ import annotations

import enum

from pydantic import BaseModel, ConfigDict


class _Frozen(BaseModel):
    """Immutable, strict base (rejects unknown fields) — matches mgf-cloud."""

    model_config = ConfigDict(frozen=True, extra="forbid")


class ServiceSpec(_Frozen):
    """Desired state of one service-bearing VM.

    ``cloud_init`` is the rendered user-data the provider injects at boot
    (install + configure the service). It is loaded/rendered by
    :mod:`mgf.cloud_provision._spec` from a spec file's ``cloud_init_file``.
    """

    name: str  # service + server name, e.g. "devpi-prod"
    flavor: str
    image: str
    provider: str = "infomaniak"
    network: str | None = "ext-net1"
    key_name: str | None = None
    security_groups: tuple[str, ...] = ()
    volume_size_gb: int | None = None
    assign_floating_ip: bool = False
    cloud_init: str | None = None
    owner: str = "mgf-cloud-provision"


class Action(enum.Enum):
    """What ``apply`` would do to converge a service to its spec."""

    CREATE = "create"  # no server with this name exists yet
    NOOP = "noop"  # a matching server already exists
    REPLACE = "replace"  # a server exists but drifts (flavor/image) — needs recreate


class ServicePlan(_Frozen):
    """The read-only outcome of ``plan`` — the action + why."""

    spec: ServiceSpec
    action: Action
    reason: str
    current_id: str | None = None  # the existing server's id, when present


class ApplyResult(_Frozen):
    """The outcome of ``apply``."""

    name: str
    action: Action
    server_id: str | None
    ip_addresses: tuple[str, ...] = ()  # routable IPs (ext-net1 private_ips are public) + FIPs


__all__ = ["Action", "ApplyResult", "ServicePlan", "ServiceSpec"]
