"""High-level API for container checkpoint/restore management."""

from __future__ import annotations

import logging
from collections.abc import Callable
from datetime import datetime
from typing import TYPE_CHECKING

from gaol.checkpoint.exceptions import (
    CheckpointNotFoundError,
    ContainerNotFoundError,
    ContainerNotRunningError,
)
from gaol.checkpoint.executor import CRIUExecutor, DockerCheckpointExecutor
from gaol.checkpoint.models import (
    CheckpointHistory,
    CheckpointMetadata,
    CheckpointPolicy,
    CheckpointResult,
    CheckpointType,
    CRIUCapabilities,
    DockerCheckpointCapabilities,
    RestoreResult,
    RuntimeType,
)
from gaol.checkpoint.store import CheckpointStore, get_checkpoint_store
from gaol.models.container import ContainerStatus
from gaol.store import get_container_store

if TYPE_CHECKING:
    from gaol.runtimes.base import ContainerRuntime
    from gaol.store import ContainerStore, StoredContainer


logger = logging.getLogger(__name__)


class CheckpointManager:
    """High-level API for container checkpoint/restore management.

    Provides a unified interface for:
    - Creating and restoring CRIU checkpoints
    - Managing checkpoint policies
    - Viewing checkpoint history
    - Checking CRIU/Docker checkpoint capabilities

    Example:
        >>> manager = get_checkpoint_manager()
        >>> result = manager.create("mycontainer", name="before-upgrade")
        >>> manager.list("mycontainer")
        >>> manager.restore("mycontainer", "before-upgrade")
    """

    def __init__(
        self,
        container_store: ContainerStore | None = None,
        checkpoint_store: CheckpointStore | None = None,
        runtime_factory: Callable[[StoredContainer], ContainerRuntime] | None = None,
    ) -> None:
        """Initialize the checkpoint manager.

        Args:
            container_store: Container store (defaults to global instance)
            checkpoint_store: Checkpoint store (defaults to global instance)
            runtime_factory: Factory function to create runtime for a container
        """
        self._container_store = container_store or get_container_store()
        self._checkpoint_store = checkpoint_store or get_checkpoint_store()
        self._runtime_factory = runtime_factory or self._default_runtime_factory

    def _default_runtime_factory(self, stored: StoredContainer) -> ContainerRuntime:
        """Create runtime for a container."""
        from gaol.runtimes import get_runtime

        return get_runtime(stored.config.runtime)

    def _get_container(self, name: str) -> StoredContainer:
        """Get a container by name or raise error."""
        stored = self._container_store.get(name)
        if stored is None:
            raise ContainerNotFoundError(name)
        return stored

    def _get_runtime_type(self, runtime_name: str) -> RuntimeType:
        """Convert runtime name to RuntimeType enum."""
        if runtime_name == "docker":
            return RuntimeType.DOCKER
        elif runtime_name == "nspawn":
            return RuntimeType.NSPAWN
        else:
            return RuntimeType.NSPAWN  # Default

    # -------------------------------------------------------------------------
    # Checkpoint Operations
    # -------------------------------------------------------------------------

    def create(
        self,
        container_name: str,
        name: str | None = None,
        *,
        leave_running: bool = False,
        tcp_established: bool = False,
    ) -> CheckpointResult:
        """Create a checkpoint of a running container.

        Captures the complete running state including memory, processes,
        and optionally network connections.

        Args:
            container_name: Name of the container
            name: Optional checkpoint name (auto-generated if not provided)
            leave_running: Keep container running after checkpoint
            tcp_established: Checkpoint established TCP connections

        Returns:
            CheckpointResult with operation outcome
        """
        stored = self._get_container(container_name)
        runtime = self._runtime_factory(stored)

        # Check if container is running
        if stored.runtime_id is None:
            raise ContainerNotRunningError(container_name)

        status = runtime.status(stored.runtime_id)
        if status.status != ContainerStatus.RUNNING:
            raise ContainerNotRunningError(container_name)

        # Get policy for configuration (optional)
        policy = self._checkpoint_store.get_policy(container_name)
        if policy:
            leave_running = leave_running or policy.leave_running
            tcp_established = tcp_established or policy.tcp_established

        # Generate checkpoint name if not provided
        if name is None:
            timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
            name = f"checkpoint-{timestamp}"

        # Create checkpoint using runtime
        result = runtime.checkpoint_create(
            container_id=stored.runtime_id,
            checkpoint_name=name,
            leave_running=leave_running,
            tcp_established=tcp_established,
        )

        # Save metadata if checkpoint succeeded
        if result.success and result.image_dir:
            metadata = CheckpointMetadata(
                checkpoint_id=result.checkpoint_id,
                container_name=container_name,
                checkpoint_name=name,
                checkpoint_type=CheckpointType.FULL,
                created_at=result.started_at,
                completed_at=result.completed_at,
                size_bytes=result.size_bytes,
                image_dir=result.image_dir,
                runtime=self._get_runtime_type(stored.config.runtime),
                container_id=stored.runtime_id,
                container_was_running=True,
                pid_at_checkpoint=result.pid_at_checkpoint,
                checksum=result.checksum,
                verified=False,
                leave_running=leave_running,
                tcp_established=tcp_established,
            )
            self._checkpoint_store.append_checkpoint(metadata)

            # Apply retention if policy exists
            if policy and policy.max_checkpoints:
                self._apply_retention(container_name, policy)

        return result

    def restore(
        self,
        container_name: str,
        checkpoint_name: str,
        *,
        tcp_established: bool = False,
    ) -> RestoreResult:
        """Restore a container from a checkpoint.

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

        Args:
            container_name: Name of the container
            checkpoint_name: Name of the checkpoint to restore
            tcp_established: Restore TCP connections

        Returns:
            RestoreResult with operation outcome
        """
        stored = self._get_container(container_name)
        runtime = self._runtime_factory(stored)

        # Verify checkpoint exists
        metadata = self._checkpoint_store.get_checkpoint_by_name(
            container_name, checkpoint_name
        )
        if metadata is None:
            raise CheckpointNotFoundError(container_name, checkpoint_name)

        # Use TCP setting from metadata if not explicitly specified
        if metadata.tcp_established:
            tcp_established = True

        # Restore using runtime
        if stored.runtime_id is None:
            raise ContainerNotRunningError(container_name)

        result = runtime.checkpoint_restore(
            container_id=stored.runtime_id,
            checkpoint_name=checkpoint_name,
            image_dir=metadata.image_dir,
            tcp_established=tcp_established,
        )

        # Update result with checkpoint ID
        result.checkpoint_id = metadata.checkpoint_id

        return result

    def list(self, container_name: str) -> list[CheckpointMetadata]:
        """List all checkpoints for a container.

        Args:
            container_name: Container name

        Returns:
            List of checkpoint metadata, newest first
        """
        self._get_container(container_name)  # Verify container exists
        return self._checkpoint_store.list_checkpoints(container_name)

    def delete(self, container_name: str, checkpoint_name: str) -> bool:
        """Delete a specific checkpoint.

        Args:
            container_name: Container name
            checkpoint_name: Checkpoint name to delete

        Returns:
            True if checkpoint was deleted
        """
        stored = self._get_container(container_name)
        runtime = self._runtime_factory(stored)

        metadata = self._checkpoint_store.get_checkpoint_by_name(
            container_name, checkpoint_name
        )
        if metadata is None:
            return False

        # Delete from runtime storage
        try:
            if stored.runtime_id:
                runtime.checkpoint_delete(
                    stored.runtime_id,
                    checkpoint_name,
                    image_dir=metadata.image_dir,
                )
        except Exception as e:
            logger.warning(f"Failed to delete checkpoint from runtime: {e}")

        # Delete checkpoint images
        self._checkpoint_store.delete_image_dir(container_name, checkpoint_name)

        # Delete metadata
        self._checkpoint_store.delete_checkpoint_metadata(
            container_name, metadata.checkpoint_id
        )

        # Update history
        history = self._checkpoint_store.get_history(container_name)
        history.remove_checkpoint(metadata.checkpoint_id)
        self._checkpoint_store.save_history(history)

        return True

    def get(
        self, container_name: str, checkpoint_name: str
    ) -> CheckpointMetadata | None:
        """Get metadata for a specific checkpoint.

        Args:
            container_name: Container name
            checkpoint_name: Checkpoint name

        Returns:
            CheckpointMetadata or None if not found
        """
        return self._checkpoint_store.get_checkpoint_by_name(
            container_name, checkpoint_name
        )

    # -------------------------------------------------------------------------
    # Policy Management
    # -------------------------------------------------------------------------

    def get_policy(self, container_name: str) -> CheckpointPolicy | None:
        """Get checkpoint policy for a container."""
        return self._checkpoint_store.get_policy(container_name)

    def set_policy(self, policy: CheckpointPolicy) -> None:
        """Set checkpoint policy for a container."""
        self._get_container(policy.container_name)
        self._checkpoint_store.save_policy(policy)

    def delete_policy(self, container_name: str) -> bool:
        """Delete checkpoint policy for a container."""
        return self._checkpoint_store.delete_policy(container_name)

    # -------------------------------------------------------------------------
    # History
    # -------------------------------------------------------------------------

    def history(
        self, container_name: str, limit: int | None = None
    ) -> CheckpointHistory:
        """Get checkpoint history for a container.

        Args:
            container_name: Container name
            limit: Maximum number of results

        Returns:
            CheckpointHistory with results
        """
        history = self._checkpoint_store.get_history(container_name)
        if limit and len(history.checkpoints) > limit:
            return CheckpointHistory(
                container_name=history.container_name,
                checkpoints=history.checkpoints[-limit:],
                last_checkpoint=history.last_checkpoint,
                last_successful_checkpoint=history.last_successful_checkpoint,
                total_checkpoints=history.total_checkpoints,
                successful_checkpoints=history.successful_checkpoints,
                failed_checkpoints=history.failed_checkpoints,
                total_size_bytes=history.total_size_bytes,
            )
        return history

    def clear_history(self, container_name: str) -> bool:
        """Clear checkpoint history for a container."""
        return self._checkpoint_store.delete_history(container_name)

    def delete_all(self, container_name: str) -> bool:
        """Delete all checkpoint data for a container.

        Removes all checkpoints, history, and policy for a container.

        Args:
            container_name: Container name

        Returns:
            True if any data was deleted
        """
        return self._checkpoint_store.delete_all_checkpoints(container_name)

    # -------------------------------------------------------------------------
    # Capability Detection
    # -------------------------------------------------------------------------

    def check_criu(self) -> CRIUCapabilities:
        """Check CRIU availability and capabilities.

        Returns:
            CRIUCapabilities with detected features
        """
        return CRIUExecutor.check_capabilities()

    def check_docker(self) -> DockerCheckpointCapabilities:
        """Check Docker checkpoint availability and capabilities.

        Returns:
            DockerCheckpointCapabilities with detected features
        """
        return DockerCheckpointExecutor.check_capabilities()

    def check_runtime(self, container_name: str) -> dict[str, bool]:
        """Check checkpoint capabilities for a specific container's runtime.

        Args:
            container_name: Container name

        Returns:
            Dictionary of capability names and availability
        """
        stored = self._get_container(container_name)
        runtime = self._runtime_factory(stored)

        try:
            return runtime.checkpoint_capabilities()
        except NotImplementedError:
            return {"available": False}

    # -------------------------------------------------------------------------
    # Retention
    # -------------------------------------------------------------------------

    def _apply_retention(
        self, container_name: str, policy: CheckpointPolicy
    ) -> list[CheckpointMetadata]:
        """Apply retention policy to checkpoints.

        Args:
            container_name: Container name
            policy: Checkpoint policy with retention settings

        Returns:
            List of deleted checkpoints
        """
        deleted = []
        checkpoints = self._checkpoint_store.list_checkpoints(container_name)

        # Sort by creation date (oldest first)
        checkpoints.sort(key=lambda c: c.created_at)

        # Apply max_checkpoints limit
        if policy.max_checkpoints and len(checkpoints) > policy.max_checkpoints:
            to_delete = checkpoints[: len(checkpoints) - policy.max_checkpoints]
            for checkpoint in to_delete:
                if self.delete(container_name, checkpoint.checkpoint_name):
                    deleted.append(checkpoint)

        # Apply max_age_days
        if policy.max_age_days:
            from datetime import timedelta

            cutoff = datetime.now() - timedelta(days=policy.max_age_days)
            remaining = self._checkpoint_store.list_checkpoints(container_name)
            for checkpoint in remaining:
                if checkpoint.created_at < cutoff and self.delete(
                    container_name, checkpoint.checkpoint_name
                ):
                    deleted.append(checkpoint)

        # Apply max_total_size_mb
        if policy.max_total_size_mb:
            max_bytes = policy.max_total_size_mb * 1024 * 1024
            remaining = self._checkpoint_store.list_checkpoints(container_name)
            remaining.sort(key=lambda c: c.created_at)  # Oldest first

            total_size = sum(c.size_bytes for c in remaining)
            while total_size > max_bytes and remaining:
                oldest = remaining.pop(0)
                if self.delete(container_name, oldest.checkpoint_name):
                    deleted.append(oldest)
                    total_size -= oldest.size_bytes

        return deleted


# Global manager instance
_manager: CheckpointManager | None = None


def get_checkpoint_manager() -> CheckpointManager:
    """Get the global checkpoint manager instance."""
    global _manager
    if _manager is None:
        _manager = CheckpointManager()
    return _manager
