"""Abstract base class for container runtimes."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from gaol.checkpoint.models import CheckpointResult, RestoreResult
    from gaol.models.container import (
        ContainerConfig,
        ContainerState,
        ContainerStats,
        Snapshot,
    )


class ContainerRuntime(ABC):
    """Abstract base class for container runtime implementations."""

    @property
    @abstractmethod
    def name(self) -> str:
        """Return the name of this runtime."""
        ...

    @abstractmethod
    def is_available(self) -> bool:
        """Check if this runtime is available on the current system."""
        ...

    @abstractmethod
    def create(self, config: ContainerConfig) -> str:
        """Create a new container.

        Args:
            config: Container configuration

        Returns:
            Container ID

        Raises:
            RuntimeError: If container creation fails
        """
        ...

    @abstractmethod
    def start(self, container_id: str) -> None:
        """Start a stopped container.

        Args:
            container_id: ID of the container to start

        Raises:
            RuntimeError: If start fails
        """
        ...

    @abstractmethod
    def stop(self, container_id: str, timeout: int = 10) -> None:
        """Stop a running container.

        Args:
            container_id: ID of the container to stop
            timeout: Seconds to wait before killing

        Raises:
            RuntimeError: If stop fails
        """
        ...

    @abstractmethod
    def destroy(self, container_id: str, force: bool = False) -> None:
        """Remove a container.

        Args:
            container_id: ID of the container to remove
            force: Force removal even if running

        Raises:
            RuntimeError: If removal fails
        """
        ...

    @abstractmethod
    def exec(
        self,
        container_id: str,
        command: list[str],
        *,
        interactive: bool = False,
        tty: bool = False,
        user: str | None = None,
        workdir: str | None = None,
        environment: dict[str, str] | None = None,
    ) -> tuple[int, str, str]:
        """Execute a command in a running container.

        Args:
            container_id: ID of the container
            command: Command and arguments to execute
            interactive: Keep STDIN open
            tty: Allocate a pseudo-TTY
            user: User to run command as
            workdir: Working directory
            environment: Additional environment variables

        Returns:
            Tuple of (exit_code, stdout, stderr)

        Raises:
            RuntimeError: If exec fails
        """
        ...

    @abstractmethod
    def list(self, all: bool = False) -> list[ContainerState]:
        """List containers.

        Args:
            all: Include stopped containers

        Returns:
            List of container states
        """
        ...

    @abstractmethod
    def status(self, container_id: str) -> ContainerState:
        """Get the status of a container.

        Args:
            container_id: ID of the container

        Returns:
            Container state

        Raises:
            RuntimeError: If container not found
        """
        ...

    @abstractmethod
    def logs(
        self,
        container_id: str,
        *,
        follow: bool = False,
        tail: int | None = None,
        since: str | None = None,
    ) -> str:
        """Get container logs.

        Args:
            container_id: ID of the container
            follow: Stream logs (blocking)
            tail: Number of lines from end
            since: Show logs since timestamp

        Returns:
            Log output
        """
        ...

    def pause(self, container_id: str) -> None:
        """Pause a running container.

        Args:
            container_id: ID of the container

        Raises:
            NotImplementedError: If runtime doesn't support pausing
        """
        raise NotImplementedError(f"{self.name} does not support pausing containers")

    def unpause(self, container_id: str) -> None:
        """Unpause a paused container.

        Args:
            container_id: ID of the container

        Raises:
            NotImplementedError: If runtime doesn't support pausing
        """
        raise NotImplementedError(f"{self.name} does not support unpausing containers")

    def copy_to(self, container_id: str, src: str, dest: str) -> None:
        """Copy a file or directory into a container.

        Args:
            container_id: ID of the container
            src: Local source path
            dest: Container destination path

        Raises:
            NotImplementedError: If runtime doesn't support copying
        """
        raise NotImplementedError(f"{self.name} does not support copying files to containers")

    def copy_from(self, container_id: str, src: str, dest: str) -> None:
        """Copy a file or directory from a container.

        Args:
            container_id: ID of the container
            src: Container source path
            dest: Local destination path

        Raises:
            NotImplementedError: If runtime doesn't support copying
        """
        raise NotImplementedError(f"{self.name} does not support copying files from containers")

    def setup(self, container_id: str) -> tuple[int, str, str]:
        """Run profile setup script in a container.

        This installs packages, creates files, and runs setup commands
        defined in the container's profiles. Should be called after start().

        Args:
            container_id: ID of the container

        Returns:
            Tuple of (exit_code, stdout, stderr)

        Raises:
            RuntimeError: If setup fails
        """
        raise NotImplementedError(f"{self.name} does not support profile setup")

    def snapshot_create(
        self, container_id: str, name: str, description: str | None = None
    ) -> Snapshot:
        """Create a snapshot of a container.

        Args:
            container_id: ID of the container
            name: Name for the snapshot
            description: Optional description

        Returns:
            Snapshot information

        Raises:
            NotImplementedError: If runtime doesn't support snapshots
            RuntimeError: If snapshot creation fails
        """
        raise NotImplementedError(f"{self.name} does not support snapshots")

    def snapshot_list(self, container_id: str) -> list[Snapshot]:
        """List snapshots for a container.

        Args:
            container_id: ID of the container

        Returns:
            List of snapshots

        Raises:
            NotImplementedError: If runtime doesn't support snapshots
        """
        raise NotImplementedError(f"{self.name} does not support snapshots")

    def snapshot_restore(self, container_id: str, snapshot_name: str) -> None:
        """Restore a container from a snapshot.

        Args:
            container_id: ID of the container
            snapshot_name: Name of the snapshot to restore

        Raises:
            NotImplementedError: If runtime doesn't support snapshots
            RuntimeError: If restore fails
        """
        raise NotImplementedError(f"{self.name} does not support snapshots")

    def snapshot_delete(self, container_id: str, snapshot_name: str) -> None:
        """Delete a snapshot.

        Args:
            container_id: ID of the container
            snapshot_name: Name of the snapshot to delete

        Raises:
            NotImplementedError: If runtime doesn't support snapshots
            RuntimeError: If deletion fails
        """
        raise NotImplementedError(f"{self.name} does not support snapshots")

    def stats(self, container_id: str) -> ContainerStats:
        """Get resource usage statistics for a container.

        Args:
            container_id: ID of the container

        Returns:
            ContainerStats with CPU, memory, network, and I/O metrics

        Raises:
            NotImplementedError: If runtime doesn't support stats
            RuntimeError: If container not found or not running
        """
        raise NotImplementedError(f"{self.name} does not support container stats")

    def export_filesystem(
        self,
        container_id: str,
        dest_path: str,
        *,
        compression: str | None = "gzip",
    ) -> str:
        """Export container filesystem to a tarball.

        Exports the entire filesystem of a container to a tar archive,
        which can be used for migration to another runtime.

        Args:
            container_id: ID of the container to export
            dest_path: Directory to write the export file to
            compression: Compression type (None, "gzip", "xz")

        Returns:
            Path to the exported tarball file

        Raises:
            NotImplementedError: If runtime doesn't support filesystem export
            RuntimeError: If export fails
        """
        raise NotImplementedError(f"{self.name} does not support filesystem export")

    def import_filesystem(
        self,
        config: ContainerConfig,
        source_path: str,
    ) -> str:
        """Create a container by importing filesystem from a tarball.

        Creates a new container and populates it with the contents of
        a tarball, typically from a migration export.

        Args:
            config: Container configuration for the new container
            source_path: Path to the tarball to import

        Returns:
            Container ID of the created container

        Raises:
            NotImplementedError: If runtime doesn't support filesystem import
            RuntimeError: If import fails
        """
        raise NotImplementedError(f"{self.name} does not support filesystem import")

    def get_filesystem_size(self, container_id: str) -> int:
        """Get the approximate size of a container's filesystem.

        Args:
            container_id: ID of the container

        Returns:
            Size in bytes

        Raises:
            NotImplementedError: If runtime doesn't support size check
            RuntimeError: If container not found
        """
        raise NotImplementedError(f"{self.name} does not support filesystem size check")

    # -------------------------------------------------------------------------
    # Checkpoint/Restore (CRIU) methods
    # -------------------------------------------------------------------------

    def checkpoint_create(
        self,
        container_id: str,
        checkpoint_name: str,
        image_dir: str | None = None,
        *,
        leave_running: bool = False,
        tcp_established: bool = False,
    ) -> "CheckpointResult":
        """Create a CRIU checkpoint of a running container.

        Captures the complete state of a running container including:
        - Process memory
        - File descriptors
        - Network connections (optional)
        - Running processes

        Args:
            container_id: ID of the container to checkpoint
            checkpoint_name: Name for the checkpoint
            image_dir: Directory to store checkpoint images (optional)
            leave_running: Keep container running after checkpoint
            tcp_established: Checkpoint established TCP connections

        Returns:
            CheckpointResult with operation status

        Raises:
            NotImplementedError: If runtime doesn't support checkpointing
            RuntimeError: If checkpoint creation fails
        """
        raise NotImplementedError(f"{self.name} does not support CRIU checkpointing")

    def checkpoint_restore(
        self,
        container_id: str,
        checkpoint_name: str,
        image_dir: str | None = None,
        *,
        tcp_established: bool = False,
    ) -> "RestoreResult":
        """Restore a container from a CRIU checkpoint.

        Resumes a container from a previously created checkpoint,
        restoring its complete state including memory and processes.

        Args:
            container_id: ID of the container to restore
            checkpoint_name: Name of the checkpoint to restore from
            image_dir: Directory containing checkpoint images
            tcp_established: Restore TCP connections

        Returns:
            RestoreResult with operation status

        Raises:
            NotImplementedError: If runtime doesn't support checkpointing
            RuntimeError: If restore fails
        """
        raise NotImplementedError(f"{self.name} does not support CRIU restore")

    def checkpoint_list(self, container_id: str) -> list[str]:
        """List available checkpoints for a container.

        Args:
            container_id: ID of the container

        Returns:
            List of checkpoint names

        Raises:
            NotImplementedError: If runtime doesn't support checkpointing
        """
        raise NotImplementedError(f"{self.name} does not support CRIU checkpointing")

    def checkpoint_delete(
        self,
        container_id: str,
        checkpoint_name: str,
        image_dir: str | None = None,
    ) -> bool:
        """Delete a checkpoint.

        Args:
            container_id: ID of the container
            checkpoint_name: Name of the checkpoint to delete
            image_dir: Directory containing checkpoint images

        Returns:
            True if checkpoint was deleted

        Raises:
            NotImplementedError: If runtime doesn't support checkpointing
            RuntimeError: If deletion fails
        """
        raise NotImplementedError(f"{self.name} does not support CRIU checkpointing")

    def checkpoint_capabilities(self) -> dict[str, bool]:
        """Get checkpoint capabilities for this runtime.

        Returns a dictionary of available checkpoint features.

        Returns:
            Dictionary with capability names and availability

        Raises:
            NotImplementedError: If runtime doesn't support checkpointing
        """
        raise NotImplementedError(f"{self.name} does not support CRIU checkpointing")
