"""Thin subprocess wrapper for ``nerdctl`` (containerd CLI).

Manages container images within the K3s/containerd ``k8s.io`` namespace
used by Rancher Desktop.
"""

from __future__ import annotations

import json
import logging
import subprocess
from dataclasses import dataclass
from pathlib import Path

logger = logging.getLogger(__name__)

_DEFAULT_NERDCTL_BIN = str(Path.home() / ".rd" / "bin" / "nerdctl")
_DEFAULT_NAMESPACE = "k8s.io"


@dataclass(frozen=True)
class ImageInfo:
    """Snapshot of a container image in nerdctl."""

    repository: str
    tag: str
    image_id: str
    size: str


class Nerdctl:
    """Typed wrapper around the ``nerdctl`` binary."""

    def __init__(
        self,
        nerdctl_bin: str = _DEFAULT_NERDCTL_BIN,
        namespace: str = _DEFAULT_NAMESPACE,
    ) -> None:
        self._bin = nerdctl_bin
        self._namespace = namespace

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

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

        Returns True on success.
        """
        cmd = [
            self._bin,
            "--namespace", self._namespace,
            "build",
            "-t", tag,
        ]
        if dockerfile:
            cmd.extend(["-f", dockerfile])
        cmd.append(context_dir)

        logger.info("Building image %s from %s", tag, context_dir)
        result = self._run(cmd, timeout=300)
        if result.returncode != 0:
            logger.error("nerdctl build failed: %s", result.stderr)
            return False
        return True

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

        Args:
            filter_name: Optional repository name filter.

        Returns:
            List of :class:`ImageInfo` instances.
        """
        cmd = [
            self._bin,
            "--namespace", self._namespace,
            "images",
            "--format", "{{json .}}",
        ]
        if filter_name:
            cmd.append(filter_name)

        result = self._run(cmd)
        if result.returncode != 0:
            logger.error("nerdctl images failed: %s", result.stderr)
            return []

        images: list[ImageInfo] = []
        for line in result.stdout.strip().splitlines():
            if not line:
                continue
            data = json.loads(line)
            images.append(
                ImageInfo(
                    repository=data.get("Repository", ""),
                    tag=data.get("Tag", ""),
                    image_id=data.get("ID", ""),
                    size=data.get("Size", ""),
                )
            )
        return images

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

        Returns True on success.
        """
        cmd = [
            self._bin,
            "--namespace", self._namespace,
            "load",
            "-i", path,
        ]
        result = self._run(cmd, timeout=120)
        if result.returncode != 0:
            logger.error("nerdctl load failed: %s", result.stderr)
            return False
        return True

    def tag(self, source: str, target: str) -> bool:
        """Tag an existing image.

        Returns True on success.
        """
        cmd = [
            self._bin,
            "--namespace", self._namespace,
            "tag",
            source,
            target,
        ]
        result = self._run(cmd)
        if result.returncode != 0:
            logger.error("nerdctl tag failed: %s", result.stderr)
            return False
        return True

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

    @staticmethod
    def _run(
        cmd: list[str], timeout: int = 60
    ) -> subprocess.CompletedProcess[str]:
        """Run a subprocess command."""
        return subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
