"""Typed, provider-agnostic resource models for mgf-cloud.

These are immutable (``frozen``) snapshots returned by a
:class:`~mgf.cloud.providers.base.CloudProvider`, plus the ``*Spec`` inputs for
create operations. They are pydantic models so they serialise cleanly for the
CLI's ``--json`` output and the web GUI. Nothing here imports a provider SDK —
adapters translate between these models and their backend's native objects.
"""

from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field

__all__ = [
    "DEFAULT_OWNER",
    "OWNER_TAG",
    "Flavor",
    "FloatingIP",
    "Image",
    "Keypair",
    "Network",
    "ObjectContainer",
    "ObjectInfo",
    "SecurityGroup",
    "SecurityRule",
    "Server",
    "ServerSpec",
    "Volume",
    "VolumeSnapshot",
    "VolumeSpec",
    "is_owned",
    "owned_metadata",
]

# The metadata key that marks a resource as federation-owned (the stateless
# identity mechanism — see docs/design/DESIGN.md §4). Confirmed 2026-06-15 against
# live Infomaniak Nova: the colon key is accepted (a tagged VM round-tripped and
# filtered by owner), so no ``mgf_owner`` fallback is needed.
OWNER_TAG = "mgf:owner"
DEFAULT_OWNER = "federation"


def owned_metadata(
    owner: str = DEFAULT_OWNER, extra: dict[str, str] | None = None
) -> dict[str, str]:
    """Build a metadata dict tagging a resource as owned by ``owner``."""
    md = {OWNER_TAG: owner}
    if extra:
        md.update(extra)
    return md


def is_owned(metadata: dict[str, str], owner: str | None = None) -> bool:
    """True if ``metadata`` carries the owner tag (optionally matching ``owner``)."""
    value = metadata.get(OWNER_TAG)
    if value is None:
        return False
    return owner is None or value == owner


class _Frozen(BaseModel):
    model_config = ConfigDict(frozen=True)


class Flavor(_Frozen):
    """A VM size (compute shape)."""

    id: str
    name: str
    vcpus: int
    ram_mb: int
    disk_gb: int


class Image(_Frozen):
    """A bootable OS image."""

    id: str
    name: str


class Keypair(_Frozen):
    """An SSH keypair registered with the provider."""

    name: str
    fingerprint: str = ""
    public_key: str = ""


class SecurityRule(_Frozen):
    """One security-group rule. ``protocol=None``/``port_*=None`` mean 'any'."""

    direction: str = "ingress"
    protocol: str | None = None
    port_min: int | None = None
    port_max: int | None = None
    remote_ip_prefix: str = "0.0.0.0/0"


class SecurityGroup(_Frozen):
    """A network security group + its rules."""

    id: str
    name: str
    description: str = ""
    rules: tuple[SecurityRule, ...] = ()


class Network(_Frozen):
    """A network the provider can attach an instance to."""

    id: str
    name: str


class FloatingIP(_Frozen):
    """A public floating IP, optionally attached to a server."""

    id: str
    address: str
    attached_server_id: str | None = None


class Volume(_Frozen):
    """A block-storage volume."""

    id: str
    name: str
    size_gb: int
    status: str = "available"
    bootable: bool = False
    attached_server_id: str | None = None


class VolumeSnapshot(_Frozen):
    """A point-in-time snapshot of a block-storage volume."""

    id: str
    name: str
    volume_id: str
    size_gb: int
    status: str = "available"


class Server(_Frozen):
    """A compute instance (VM)."""

    id: str
    name: str
    status: str = "ACTIVE"
    flavor: str = ""
    image: str = ""
    key_name: str | None = None
    private_ips: tuple[str, ...] = ()
    floating_ips: tuple[str, ...] = ()
    security_groups: tuple[str, ...] = ()
    metadata: dict[str, str] = Field(default_factory=dict)
    created_at: str | None = None


class ObjectContainer(_Frozen):
    """An object-storage container (Swift container / S3 bucket)."""

    name: str
    object_count: int = 0
    bytes: int = 0


class ObjectInfo(_Frozen):
    """Metadata for one stored object."""

    name: str
    bytes: int = 0
    content_type: str = "application/octet-stream"
    last_modified: str | None = None
    etag: str = ""


class ServerSpec(_Frozen):
    """The inputs to create a server. ``volume_size_gb=None`` boots from an
    ephemeral disk; a value boots from a new persistent volume of that size."""

    name: str
    flavor: str
    image: str
    network: str | None = None
    key_name: str | None = None
    security_groups: tuple[str, ...] = ()
    user_data: str | None = None
    volume_size_gb: int | None = None
    assign_floating_ip: bool = False
    metadata: dict[str, str] = Field(default_factory=dict)


class VolumeSpec(_Frozen):
    """The inputs to create a block-storage volume."""

    name: str
    size_gb: int
    bootable: bool = False
