"""Tests for the nerdctl CLI wrapper.

Tests argument construction, JSON-line parsing, and failure handling
with mocked subprocess calls. No nerdctl binary required.
"""

from __future__ import annotations

import dataclasses
import subprocess
from unittest.mock import patch

import pytest

from citadel_operator.k3s.nerdctl import ImageInfo, Nerdctl


def _completed(
    returncode: int = 0,
    stdout: str = "",
    stderr: str = "",
) -> subprocess.CompletedProcess[str]:
    """Build a fake CompletedProcess."""
    return subprocess.CompletedProcess(
        args=[], returncode=returncode, stdout=stdout, stderr=stderr,
    )


_IMAGES_JSONL = (
    '{"Repository":"myapp","Tag":"latest","ID":"sha256:abc123","Size":"50MB"}\n'
    '{"Repository":"myapp","Tag":"v1.0","ID":"sha256:def456","Size":"52MB"}\n'
)


class TestNerdctlBuild:
    """Tests for Nerdctl.build() argument construction."""

    @patch.object(Nerdctl, "_run", return_value=_completed())
    def test_basic_build(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl", namespace="k8s.io")
        result = n.build("/app", "myimage:latest")

        assert result is True
        mock_run.assert_called_once_with(
            [
                "/usr/bin/nerdctl", "--namespace", "k8s.io",
                "build", "-t", "myimage:latest", "/app",
            ],
            timeout=300,
        )

    @patch.object(Nerdctl, "_run", return_value=_completed())
    def test_build_with_dockerfile(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        result = n.build("/app", "myimage:v2", dockerfile="Dockerfile.prod")

        assert result is True
        call_args = mock_run.call_args[0][0]
        assert "-f" in call_args
        assert "Dockerfile.prod" in call_args
        assert call_args[-1] == "/app"

    @patch.object(
        Nerdctl, "_run",
        return_value=_completed(returncode=1, stderr="build error"),
    )
    def test_build_failure_returns_false(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        assert n.build("/app", "bad:tag") is False


class TestNerdctlImages:
    """Tests for Nerdctl.images() JSON-line parsing."""

    @patch.object(Nerdctl, "_run", return_value=_completed(stdout=_IMAGES_JSONL))
    def test_parses_json_lines(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        images = n.images()

        assert len(images) == 2
        assert all(isinstance(img, ImageInfo) for img in images)
        assert images[0].repository == "myapp"
        assert images[0].tag == "latest"
        assert images[0].image_id == "sha256:abc123"
        assert images[0].size == "50MB"
        assert images[1].tag == "v1.0"

    @patch.object(Nerdctl, "_run", return_value=_completed(stdout=_IMAGES_JSONL))
    def test_passes_namespace_arg(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl", namespace="k8s.io")
        n.images()

        call_args = mock_run.call_args[0][0]
        ns_idx = call_args.index("--namespace")
        assert call_args[ns_idx + 1] == "k8s.io"

    @patch.object(Nerdctl, "_run", return_value=_completed(stdout=_IMAGES_JSONL))
    def test_passes_filter_name(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        n.images(filter_name="myapp")

        call_args = mock_run.call_args[0][0]
        assert call_args[-1] == "myapp"

    @patch.object(Nerdctl, "_run", return_value=_completed(stdout=""))
    def test_empty_output_returns_empty_list(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        assert n.images() == []

    @patch.object(
        Nerdctl, "_run",
        return_value=_completed(returncode=1, stderr="daemon error"),
    )
    def test_failure_returns_empty_list(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        assert n.images() == []


class TestNerdctlLoad:
    """Tests for Nerdctl.load()."""

    @patch.object(Nerdctl, "_run", return_value=_completed())
    def test_passes_correct_args(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl", namespace="k8s.io")
        result = n.load("/tmp/image.tar")

        assert result is True
        mock_run.assert_called_once_with(
            [
                "/usr/bin/nerdctl", "--namespace", "k8s.io",
                "load", "-i", "/tmp/image.tar",
            ],
            timeout=120,
        )

    @patch.object(
        Nerdctl, "_run",
        return_value=_completed(returncode=1, stderr="load error"),
    )
    def test_failure_returns_false(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        assert n.load("/tmp/bad.tar") is False


class TestNerdctlTag:
    """Tests for Nerdctl.tag()."""

    @patch.object(Nerdctl, "_run", return_value=_completed())
    def test_passes_correct_args(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl", namespace="k8s.io")
        result = n.tag("myapp:latest", "registry.io/myapp:v1")

        assert result is True
        mock_run.assert_called_once_with(
            [
                "/usr/bin/nerdctl", "--namespace", "k8s.io",
                "tag", "myapp:latest", "registry.io/myapp:v1",
            ],
        )

    @patch.object(
        Nerdctl, "_run",
        return_value=_completed(returncode=1, stderr="tag error"),
    )
    def test_failure_returns_false(self, mock_run):
        n = Nerdctl("/usr/bin/nerdctl")
        assert n.tag("a", "b") is False


class TestImageInfoFrozen:
    """ImageInfo must be a frozen dataclass."""

    def test_is_frozen(self):
        img = ImageInfo(
            repository="myapp", tag="latest",
            image_id="sha256:abc", size="50MB",
        )
        with pytest.raises(dataclasses.FrozenInstanceError):
            img.tag = "v2"  # type: ignore[misc]

    def test_is_dataclass(self):
        assert dataclasses.is_dataclass(ImageInfo)
