from unittest.mock import Mock, patch

from tests.helpers import PluginsTestCase, temporary_root
from tutor import images, plugins, utils
from tutor.__about__ import __version__
from tutor.commands.images import ImageNotFoundError

from .base import TestCommandMixin


class ImagesTests(PluginsTestCase, TestCommandMixin):
    def test_images_help(self) -> None:
        result = self.invoke(["images", "--help"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)

    def test_images_pull_image(self) -> None:
        result = self.invoke(["images", "pull"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)

    def test_images_pull_plugin_invalid_plugin_should_throw_error(self) -> None:
        result = self.invoke(["images", "pull", "plugin"])
        self.assertEqual(1, result.exit_code)
        self.assertEqual(ImageNotFoundError, type(result.exception))

    @patch.object(images, "pull", return_value=None)
    def test_images_pull_plugin(self, image_pull: Mock) -> None:
        plugins.v0.DictPlugin(
            {
                "name": "plugin1",
                "hooks": {
                    "remote-image": {
                        "service1": "service1:1.0.0",
                        "service2": "service2:2.0.0",
                    }
                },
            }
        )
        plugins.load("plugin1")
        result = self.invoke(["images", "pull", "service1"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
        image_pull.assert_called_once_with("service1:1.0.0")

    @patch.object(images, "pull", return_value=None)
    def test_images_pull_all_vendor_images(self, image_pull: Mock) -> None:
        result = self.invoke(["images", "pull", "mysql"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
        # Note: we should update this tag whenever the mysql image is updated
        image_pull.assert_called_once_with("docker.io/mysql:8.1.0")

    def test_images_printtag_image(self) -> None:
        result = self.invoke(["images", "printtag", "openedx"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
        self.assertRegex(
            result.output, rf"docker.io/overhangio/openedx:{__version__}\n"
        )

    def test_images_printtag_plugin(self) -> None:
        plugins.v0.DictPlugin(
            {
                "name": "plugin1",
                "hooks": {
                    "build-image": {
                        "service1": "service1:1.0.0",
                        "service2": "service2:2.0.0",
                    }
                },
            }
        )
        plugins.load("plugin1")
        result = self.invoke(["images", "printtag", "service1"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code, result)
        self.assertEqual(result.output, "service1:1.0.0\n")

    @patch.object(images, "build", return_value=None)
    def test_images_build_plugin(self, mock_image_build: Mock) -> None:
        plugins.v0.DictPlugin(
            {
                "name": "plugin1",
                "hooks": {
                    "build-image": {
                        "service1": "service1:1.0.0",
                        "service2": "service2:2.0.0",
                    }
                },
            }
        )
        plugins.load("plugin1")
        with temporary_root() as root:
            self.invoke_in_root(root, ["config", "save"])
            result = self.invoke_in_root(root, ["images", "build", "service1"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
        mock_image_build.assert_called()
        self.assertIn("service1:1.0.0", mock_image_build.call_args[0])

    @patch.object(images, "build", return_value=None)
    def test_images_build_plugin_with_args(self, image_build: Mock) -> None:
        plugins.v0.DictPlugin(
            {
                "name": "plugin1",
                "hooks": {
                    "build-image": {
                        "service1": "service1:1.0.0",
                        "service2": "service2:2.0.0",
                    }
                },
            }
        )
        plugins.load("plugin1")
        build_args = [
            "images",
            "build",
            "--no-cache",
            "-a",
            "myarg=value",
            "--add-host",
            "host",
            "--target",
            "target",
            "-d",
            "docker_args",
            "service1",
        ]
        with temporary_root() as root:
            utils.is_buildkit_enabled.cache_clear()
            with patch.object(utils, "is_buildkit_enabled", return_value=False):
                self.invoke_in_root(root, ["config", "save"])
                result = self.invoke_in_root(root, build_args)
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
        image_build.assert_called()
        self.assertIn("service1:1.0.0", image_build.call_args[0])
        self.assertEqual(
            [
                "service1:1.0.0",
                "--no-cache",
                "--build-arg",
                "myarg=value",
                "--add-host",
                "host",
                "--target",
                "target",
                "docker_args",
            ],
            list(image_build.call_args[0][1:]),
        )

    def test_images_push(self) -> None:
        result = self.invoke(["images", "push"])
        self.assertIsNone(result.exception)
        self.assertEqual(0, result.exit_code)
