"""The provider-agnostic ``CloudProvider`` interface.

Every adapter (Infomaniak/OpenStack, AWS, the in-memory fake) implements this
Protocol. It speaks only in :mod:`mgf.cloud.models` types — no backend objects
leak across it — so the CLI, the web GUI, and mgf-fed all program against one
surface. Operations are synchronous and return immutable model snapshots; a
missing resource raises :class:`mgf.common.exceptions.ResourceNotFoundError`.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    from mgf.cloud.models import (
        Flavor,
        FloatingIP,
        Image,
        Keypair,
        Network,
        ObjectContainer,
        ObjectInfo,
        SecurityGroup,
        SecurityRule,
        Server,
        ServerSpec,
        Volume,
        VolumeSnapshot,
        VolumeSpec,
    )

__all__ = ["CloudProvider"]


@runtime_checkable
class CloudProvider(Protocol):
    """The operations every mgf-cloud provider supports."""

    name: str

    # --- compute -------------------------------------------------------------
    def list_servers(self, owner: str | None = None) -> list[Server]:
        """List servers, optionally filtered to those tagged with ``owner``."""
        ...

    def get_server(self, name_or_id: str) -> Server:
        """Return one server by id or name (raises if absent)."""
        ...

    def create_server(self, spec: ServerSpec) -> Server:
        """Create a server from ``spec`` and return its snapshot."""
        ...

    def delete_server(self, name_or_id: str) -> None:
        """Delete a server (detaching its floating IPs + volumes first)."""
        ...

    def reboot_server(self, name_or_id: str, *, hard: bool = False) -> None:
        """Reboot a server (soft by default, hard if requested)."""
        ...

    def resize_server(self, name_or_id: str, flavor: str) -> None:
        """Resize a server to a new flavour."""
        ...

    def snapshot_server(self, name_or_id: str, snapshot_name: str) -> Image:
        """Create an image snapshot of a server's boot disk."""
        ...

    def list_flavors(self) -> list[Flavor]:
        """List available VM flavours."""
        ...

    def list_images(self) -> list[Image]:
        """List available OS images."""
        ...

    # --- keypairs ------------------------------------------------------------
    def list_keypairs(self) -> list[Keypair]:
        """List registered SSH keypairs."""
        ...

    def create_keypair(self, name: str, public_key: str) -> Keypair:
        """Register an SSH public key under ``name``."""
        ...

    def delete_keypair(self, name: str) -> None:
        """Remove a registered keypair."""
        ...

    # --- block storage -------------------------------------------------------
    def list_volumes(self) -> list[Volume]:
        """List block-storage volumes."""
        ...

    def get_volume(self, name_or_id: str) -> Volume:
        """Return one volume by id or name (raises if absent)."""
        ...

    def create_volume(self, spec: VolumeSpec) -> Volume:
        """Create a block-storage volume."""
        ...

    def attach_volume(self, volume: str, server: str) -> None:
        """Attach a volume to a server."""
        ...

    def detach_volume(self, volume: str, server: str) -> None:
        """Detach a volume from a server."""
        ...

    def delete_volume(self, volume: str) -> None:
        """Delete a block-storage volume."""
        ...

    def snapshot_volume(self, volume: str, snapshot_name: str) -> VolumeSnapshot:
        """Create a snapshot of a volume."""
        ...

    # --- network -------------------------------------------------------------
    def list_networks(self) -> list[Network]:
        """List attachable networks."""
        ...

    def list_floating_ips(self) -> list[FloatingIP]:
        """List allocated floating IPs."""
        ...

    def create_floating_ip(self, network: str | None = None) -> FloatingIP:
        """Allocate a floating IP (from ``network`` if given)."""
        ...

    def attach_floating_ip(self, address: str, server: str) -> None:
        """Attach a floating IP to a server."""
        ...

    def detach_floating_ip(self, address: str) -> None:
        """Detach a floating IP from whatever it's attached to."""
        ...

    def release_floating_ip(self, address: str) -> None:
        """Release (deallocate) a floating IP."""
        ...

    def list_security_groups(self) -> list[SecurityGroup]:
        """List security groups."""
        ...

    def create_security_group(
        self,
        name: str,
        description: str = "",
        rules: tuple[SecurityRule, ...] = (),
    ) -> SecurityGroup:
        """Create a security group with ``rules``."""
        ...

    def delete_security_group(self, name_or_id: str) -> None:
        """Delete a security group."""
        ...

    # --- object storage ------------------------------------------------------
    def list_containers(self) -> list[ObjectContainer]:
        """List object-storage containers."""
        ...

    def create_container(self, name: str) -> ObjectContainer:
        """Create an object-storage container."""
        ...

    def delete_container(self, name: str) -> None:
        """Delete an object-storage container."""
        ...

    def list_objects(self, container: str) -> list[ObjectInfo]:
        """List the objects in a container."""
        ...

    def put_object(
        self,
        container: str,
        name: str,
        data: bytes,
        content_type: str = "application/octet-stream",
    ) -> ObjectInfo:
        """Store ``data`` as an object."""
        ...

    def get_object(self, container: str, name: str) -> bytes:
        """Fetch an object's bytes."""
        ...

    def delete_object(self, container: str, name: str) -> None:
        """Delete an object."""
        ...
