"""Rancher Desktop provider for Citadel cluster management.

Replaces the Multipass provider — Rancher Desktop is a single-node K3s
environment managed through ``rdctl`` and ``nerdctl``. This is the local
development substrate that Apollo deploys onto.
"""

from __future__ import annotations

import logging
import subprocess
from dataclasses import dataclass
from enum import Enum

from citadel_operator.k3s.nerdctl import ImageInfo, Nerdctl
from citadel_operator.k3s.rdctl import Rdctl

logger = logging.getLogger(__name__)

DEFAULT_KUBE_CONTEXT = "rancher-desktop"
HOST_FROM_CLUSTER = "host.rancher-desktop.internal"


class ClusterState(Enum):
    """Possible states of the Rancher Desktop K3s cluster."""

    RUNNING = "running"
    STOPPED = "stopped"
    STARTING = "starting"
    ERROR = "error"
    UNKNOWN = "unknown"


@dataclass(frozen=True)
class ClusterInfo:
    """Snapshot of the Rancher Desktop K3s cluster."""

    state: ClusterState
    kubernetes_version: str = ""
    node_name: str = ""
    node_ready: bool = False
    flannel_enabled: bool = True
    traefik_enabled: bool = True
    container_engine: str = "containerd"


@dataclass(frozen=True)
class ResetResult:
    """Outcome of a K3s factory-reset operation."""

    success: bool
    previous_state: ClusterState
    new_state: ClusterState
    error: str | None = None


class RancherDesktopProvider:
    """Manages a Rancher Desktop K3s environment for Citadel.

    Provides cluster lifecycle, VM shell access, and image management.
    """

    def __init__(
        self,
        rdctl_bin: str = "~/.rd/bin/rdctl",
        nerdctl_bin: str = "~/.rd/bin/nerdctl",
        kube_context: str = DEFAULT_KUBE_CONTEXT,
    ) -> None:
        from pathlib import Path

        self._rdctl = Rdctl(str(Path(rdctl_bin).expanduser()))
        self._nerdctl = Nerdctl(str(Path(nerdctl_bin).expanduser()))
        self._kube_context = kube_context

    # ------------------------------------------------------------------
    # Cluster lifecycle
    # ------------------------------------------------------------------

    def get_status(self) -> ClusterInfo:
        """Query Rancher Desktop for current cluster state."""
        try:
            settings = self._rdctl.list_settings()
        except Exception as exc:
            logger.error("Failed to query rdctl: %s", exc)
            return ClusterInfo(state=ClusterState.UNKNOWN)

        # Determine state by checking if kubectl can reach the API server
        state = ClusterState.RUNNING if self.is_ready() else ClusterState.STOPPED

        # Try to get node name
        node_name = ""
        node_ready = False
        try:
            result = self._kubectl(
                "get", "nodes",
                "-o", "jsonpath={.items[0].metadata.name}",
            )
            if result.returncode == 0 and result.stdout.strip():
                node_name = result.stdout.strip()
                node_ready = True
        except Exception:
            pass

        return ClusterInfo(
            state=state,
            kubernetes_version=settings.kubernetes_version,
            node_name=node_name,
            node_ready=node_ready,
            flannel_enabled=settings.flannel_enabled,
            traefik_enabled=settings.traefik_enabled,
            container_engine=settings.container_engine,
        )

    def is_ready(self) -> bool:
        """Check whether the K3s API server is reachable."""
        try:
            result = self._kubectl("cluster-info")
            return result.returncode == 0
        except Exception:
            return False

    def configure_for_cilium(self) -> bool:
        """Disable Flannel/Traefik and fix mount propagation for Cilium.

        Returns True on success.
        """
        logger.info("Configuring K3s for Cilium (flannel=false, traefik=false)")
        result = self._rdctl.set(
            kubernetes_options_flannel=False,
            kubernetes_options_traefik=False,
        )
        if result.returncode != 0:
            logger.error("rdctl set failed: %s", result.stderr)
            return False

        # Fix mount propagation for Cilium's BPF filesystem
        mount_result = self._rdctl.shell(
            "sudo", "mount", "--make-rshared", "/",
        )
        if mount_result.returncode != 0:
            logger.warning("mount --make-rshared failed: %s", mount_result.stderr)

        return True

    def reset_k3s(self) -> ResetResult:
        """Factory-reset Rancher Desktop's K3s.

        Returns a :class:`ResetResult` with the outcome.
        """
        previous = self.get_status().state
        logger.info("Factory-resetting K3s (previous state: %s)", previous.value)

        result = self._rdctl.factory_reset()
        if result.returncode != 0:
            return ResetResult(
                success=False,
                previous_state=previous,
                new_state=ClusterState.ERROR,
                error=result.stderr,
            )

        return ResetResult(
            success=True,
            previous_state=previous,
            new_state=ClusterState.STOPPED,
        )

    def shutdown(self) -> bool:
        """Shut down Rancher Desktop."""
        result = self._rdctl.shutdown()
        if result.returncode != 0:
            logger.error("rdctl shutdown failed: %s", result.stderr)
            return False
        return True

    def start(self) -> bool:
        """Start Rancher Desktop."""
        result = self._rdctl.start()
        if result.returncode != 0:
            logger.error("rdctl start failed: %s", result.stderr)
            return False
        return True

    # ------------------------------------------------------------------
    # VM access
    # ------------------------------------------------------------------

    def shell(self, *cmd: str) -> subprocess.CompletedProcess[str]:
        """Execute a command inside the Rancher Desktop VM."""
        return self._rdctl.shell(*cmd)

    # ------------------------------------------------------------------
    # Image management
    # ------------------------------------------------------------------

    def build_image(
        self,
        context_dir: str,
        tag: str,
        dockerfile: str | None = None,
    ) -> bool:
        """Build a container image via nerdctl."""
        return self._nerdctl.build(context_dir, tag, dockerfile=dockerfile)

    def list_images(self, filter_name: str | None = None) -> list[ImageInfo]:
        """List container images in the k8s.io namespace."""
        return self._nerdctl.images(filter_name=filter_name)

    def load_image(self, path: str) -> bool:
        """Load a container image from a tar archive."""
        return self._nerdctl.load(path)

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    def _kubectl(self, *args: str) -> subprocess.CompletedProcess[str]:
        """Run kubectl against this provider's context."""
        return subprocess.run(
            ["kubectl", "--context", self._kube_context, *args],
            capture_output=True,
            text=True,
            timeout=30,
            check=False,
        )
