"""Tests for ``ilum airgap`` commands."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest
from typer.testing import CliRunner

from ilum.cli.main import app
from ilum.core.helm import HelmResult


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


SAMPLE_TEMPLATE_OUTPUT = """\
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ilum-core
spec:
  template:
    spec:
      containers:
        - name: core
          image: docker.io/ilum/ilum-core:6.7.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ilum-ui
spec:
  template:
    spec:
      containers:
        - name: ui
          image: docker.io/ilum/ilum-ui:6.7.0
"""


class TestAirgapImages:
    def test_airgap_images_table_output(self, runner: CliRunner) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.default_enabled.return_value = ["core", "ui"]
            mock_resolver.resolve_enables.return_value = [
                "ilum-core.enabled=true",
                "ilum-ui.enabled=true",
            ]
            mock_resolver.all_modules.return_value = []
            result = runner.invoke(app, ["airgap", "images"])
        assert result.exit_code == 0
        assert "ilum-core" in result.output
        assert "ilum-ui" in result.output

    def test_airgap_images_plain_output(self, runner: CliRunner) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.default_enabled.return_value = ["core"]
            mock_resolver.resolve_enables.return_value = ["ilum-core.enabled=true"]
            mock_resolver.all_modules.return_value = []
            result = runner.invoke(app, ["airgap", "images", "--format", "plain"])
        assert result.exit_code == 0
        lines = [line.strip() for line in result.output.strip().splitlines() if line.strip()]
        assert any("docker.io/ilum/ilum-core:6.7.0" in line for line in lines)

    def test_airgap_images_with_preset(self, runner: CliRunner) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.resolve_enables.return_value = []
            mock_resolver.all_modules.return_value = []
            result = runner.invoke(app, ["airgap", "images", "--preset", "development"])
        assert result.exit_code == 0

    def test_airgap_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["airgap", "--help"])
        assert result.exit_code == 0
        assert "Air-gapped" in result.output or "airgap" in result.output.lower()


class TestAirgapExport:
    def test_export_creates_manifest(self, runner: CliRunner, tmp_path) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        output_dir = tmp_path / "bundle"
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
            patch("ilum.cli.airgap_cmd.subprocess") as mock_subprocess,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.default_enabled.return_value = ["core"]
            mock_resolver.resolve_enables.return_value = []
            mock_resolver.all_modules.return_value = []
            mock_subprocess.run.return_value = MagicMock(returncode=0, stdout="", stderr="")
            result = runner.invoke(app, ["airgap", "export", str(output_dir)])
        assert result.exit_code == 0
        manifest = output_dir / "manifest.json"
        assert manifest.exists()
        data = json.loads(manifest.read_text())
        assert "images" in data
        assert "modules" in data

    def test_export_creates_image_directory(self, runner: CliRunner, tmp_path) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        output_dir = tmp_path / "bundle"
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
            patch("ilum.cli.airgap_cmd.subprocess") as mock_subprocess,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.default_enabled.return_value = ["core"]
            mock_resolver.resolve_enables.return_value = []
            mock_resolver.all_modules.return_value = []
            mock_subprocess.run.return_value = MagicMock(returncode=0, stdout="", stderr="")
            result = runner.invoke(app, ["airgap", "export", str(output_dir)])
        assert result.exit_code == 0
        assert (output_dir / "images").is_dir()

    def test_export_docker_pull_failure(self, runner: CliRunner, tmp_path) -> None:
        mock_helm = MagicMock()
        mock_helm.template.return_value = HelmResult(
            returncode=0, stdout=SAMPLE_TEMPLATE_OUTPUT, stderr="", command=[]
        )
        output_dir = tmp_path / "bundle"
        with (
            patch("ilum.cli.airgap_cmd.HelmClient", return_value=mock_helm),
            patch("ilum.cli.airgap_cmd.ModuleResolver") as mock_resolver_cls,
            patch("ilum.cli.airgap_cmd.subprocess") as mock_subprocess,
        ):
            mock_resolver = mock_resolver_cls.return_value
            mock_resolver.default_enabled.return_value = ["core"]
            mock_resolver.resolve_enables.return_value = []
            mock_resolver.all_modules.return_value = []
            mock_subprocess.run.return_value = MagicMock(
                returncode=1, stdout="", stderr="pull failed"
            )
            result = runner.invoke(app, ["airgap", "export", str(output_dir)])
        assert result.exit_code == 1


class TestAirgapImport:
    def test_import_loads_and_pushes(self, runner: CliRunner, tmp_path) -> None:
        bundle = tmp_path / "bundle"
        bundle.mkdir()
        images_dir = bundle / "images"
        images_dir.mkdir()
        (images_dir / "core.tar").write_bytes(b"fake")
        manifest = {
            "version": "6.7.0",
            "images": [{"reference": "docker.io/ilum/ilum-core:6.7.0", "file": "images/core.tar"}],
            "modules": ["core"],
        }
        (bundle / "manifest.json").write_text(json.dumps(manifest))

        with patch("ilum.cli.airgap_cmd.subprocess") as mock_subprocess:
            mock_subprocess.run.return_value = MagicMock(returncode=0, stdout="", stderr="")
            result = runner.invoke(
                app, ["airgap", "import", str(bundle), "--registry", "registry.local:5000"]
            )
        assert result.exit_code == 0
        assert "registry.local:5000" in result.output

    def test_import_missing_manifest(self, runner: CliRunner, tmp_path) -> None:
        bundle = tmp_path / "empty"
        bundle.mkdir()
        result = runner.invoke(
            app, ["airgap", "import", str(bundle), "--registry", "registry.local:5000"]
        )
        assert result.exit_code == 1
        assert "manifest.json" in result.output

    def test_import_requires_registry_flag(self, runner: CliRunner, tmp_path) -> None:
        bundle = tmp_path / "bundle"
        bundle.mkdir()
        (bundle / "manifest.json").write_text("{}")
        result = runner.invoke(app, ["airgap", "import", str(bundle)])
        assert result.exit_code != 0  # missing required --registry
