"""
    :codeauthor: Jochen Breuer <jbreuer@suse.de>
"""

# pylint: disable=no-value-for-parameter
import os
from contextlib import contextmanager

import pytest

import salt.utils.files
import salt.utils.platform
from salt.modules import kubernetesmod as kubernetes
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import Mock, patch
from tests.support.unit import TestCase


@contextmanager
def mock_kubernetes_library():
    """
    After fixing the bug in 1c821c0e77de58892c77d8e55386fac25e518c31,
    it caused kubernetes._cleanup() to get called for virtually every
    test, which blows up. This prevents that specific blow-up once
    """
    with patch("salt.modules.kubernetesmod.kubernetes") as mock_kubernetes_lib:
        yield mock_kubernetes_lib


@pytest.mark.skipif(
    not kubernetes.HAS_LIBS,
    reason="Kubernetes client lib is not installed. Skipping test_kubernetes.py",
)
class KubernetesTestCase(TestCase, LoaderModuleMockMixin):
    """
    Test cases for salt.modules.kubernetesmod
    """

    def setup_loader_modules(self):
        return {kubernetes: {"__salt__": {}}}

    def test_nodes(self):
        """
        Test node listing.
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
                    **{
                        "list_node.return_value.to_dict.return_value": {
                            "items": [{"metadata": {"name": "mock_node_name"}}]
                        }
                    }
                )
                self.assertEqual(kubernetes.nodes(), ["mock_node_name"])
                self.assertTrue(
                    kubernetes.kubernetes.client.CoreV1Api().list_node().to_dict.called
                )

    def test_deployments(self):
        """
        Tests deployment listing.
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
                    **{
                        "list_namespaced_deployment.return_value.to_dict.return_value": {
                            "items": [{"metadata": {"name": "mock_deployment_name"}}]
                        }
                    }
                )
                self.assertEqual(kubernetes.deployments(), ["mock_deployment_name"])
                # pylint: disable=E1120
                self.assertTrue(
                    kubernetes.kubernetes.client.ExtensionsV1beta1Api()
                    .list_namespaced_deployment()
                    .to_dict.called
                )
                # pylint: enable=E1120

    def test_services(self):
        """
        Tests services listing.
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
                    **{
                        "list_namespaced_service.return_value.to_dict.return_value": {
                            "items": [{"metadata": {"name": "mock_service_name"}}]
                        }
                    }
                )
                self.assertEqual(kubernetes.services(), ["mock_service_name"])
                # pylint: disable=E1120
                self.assertTrue(
                    kubernetes.kubernetes.client.CoreV1Api()
                    .list_namespaced_service()
                    .to_dict.called
                )
                # pylint: enable=E1120

    def test_pods(self):
        """
        Tests pods listing.
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
                    **{
                        "list_namespaced_pod.return_value.to_dict.return_value": {
                            "items": [{"metadata": {"name": "mock_pod_name"}}]
                        }
                    }
                )
                self.assertEqual(kubernetes.pods(), ["mock_pod_name"])
                # pylint: disable=E1120
                self.assertTrue(
                    kubernetes.kubernetes.client.CoreV1Api()
                    .list_namespaced_pod()
                    .to_dict.called
                )
                # pylint: enable=E1120

    def test_delete_deployments(self):
        """
        Tests deployment deletion
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch(
                "salt.modules.kubernetesmod.show_deployment", Mock(return_value=None)
            ):
                with patch.dict(
                    kubernetes.__salt__,
                    {"config.option": Mock(side_effect=self.settings)},
                ):
                    mock_kubernetes_lib.client.V1DeleteOptions = Mock(return_value="")
                    mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
                        **{
                            "delete_namespaced_deployment.return_value.to_dict.return_value": {
                                "code": ""
                            }
                        }
                    )
                    self.assertEqual(
                        kubernetes.delete_deployment("test"), {"code": 200}
                    )
                    # pylint: disable=E1120
                    self.assertTrue(
                        kubernetes.kubernetes.client.ExtensionsV1beta1Api()
                        .delete_namespaced_deployment()
                        .to_dict.called
                    )
                    # pylint: enable=E1120

    def test_create_deployments(self):
        """
        Tests deployment creation.
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
                    **{
                        "create_namespaced_deployment.return_value.to_dict.return_value": {}
                    }
                )
                self.assertEqual(
                    kubernetes.create_deployment(
                        "test", "default", {}, {}, None, None, None
                    ),
                    {},
                )
                # pylint: disable=E1120
                self.assertTrue(
                    kubernetes.kubernetes.client.ExtensionsV1beta1Api()
                    .create_namespaced_deployment()
                    .to_dict.called
                )
                # pylint: enable=E1120

    @staticmethod
    def settings(name, value=None):
        """
        Test helper
        :return: settings or default
        """
        data = {
            "kubernetes.kubeconfig": "/home/testuser/.minikube/kubeconfig.cfg",
            "kubernetes.context": "minikube",
        }
        return data.get(name, value)

    def test_setup_kubeconfig_file(self):
        """
        Test that the `kubernetes.kubeconfig` configuration isn't overwritten
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.config.load_kube_config = Mock()
                config = kubernetes._setup_conn()
                self.assertEqual(
                    self.settings("kubernetes.kubeconfig"),
                    config["kubeconfig"],
                )

    def test_setup_kubeconfig_data_overwrite(self):
        """
        Test that provided `kubernetes.kubeconfig` configuration is overwritten
        by provided kubeconfig_data in the command
        :return:
        """
        with mock_kubernetes_library() as mock_kubernetes_lib:
            with patch.dict(
                kubernetes.__salt__, {"config.option": Mock(side_effect=self.settings)}
            ):
                mock_kubernetes_lib.config.load_kube_config = Mock()
                config = kubernetes._setup_conn(
                    kubeconfig_data="MTIzNDU2Nzg5MAo=", context="newcontext"
                )
                check_path = os.path.join("/tmp", "salt-kubeconfig-")
                if salt.utils.platform.is_windows():
                    check_path = os.path.join(os.environ.get("TMP"), "salt-kubeconfig-")
                elif salt.utils.platform.is_darwin():
                    check_path = os.path.join(
                        os.environ.get("TMPDIR", "/tmp"), "salt-kubeconfig-"
                    )
                self.assertTrue(
                    config["kubeconfig"].lower().startswith(check_path.lower())
                )
                self.assertTrue(os.path.exists(config["kubeconfig"]))
                with salt.utils.files.fopen(config["kubeconfig"], "r") as kcfg:
                    self.assertEqual("1234567890\n", kcfg.read())
                kubernetes._cleanup(**config)

    def test_node_labels(self):
        """
        Test kubernetes.node_labels
        :return:
        """
        with patch("salt.modules.kubernetesmod.node") as mock_node:
            mock_node.return_value = {
                "metadata": {
                    "labels": {
                        "kubernetes.io/hostname": "minikube",
                        "kubernetes.io/os": "linux",
                    }
                }
            }
            self.assertEqual(
                kubernetes.node_labels("minikube"),
                {"kubernetes.io/hostname": "minikube", "kubernetes.io/os": "linux"},
            )

    def test_adding_change_cause_annotation(self):
        """
        Tests adding a `kubernetes.io/change-cause` annotation just like
        kubectl [apply|create|replace] --record does
        :return:
        """
        with patch(
            "salt.modules.kubernetesmod.sys.argv", ["/usr/bin/salt-call", "state.apply"]
        ) as mock_sys:
            func = getattr(kubernetes, "__dict_to_object_meta")
            data = func(name="test-pod", namespace="test", metadata={})

            self.assertEqual(data.name, "test-pod")
            self.assertEqual(data.namespace, "test")
            self.assertEqual(
                data.annotations,
                {"kubernetes.io/change-cause": "/usr/bin/salt-call state.apply"},
            )

            # Ensure any specified annotations aren't overwritten
            test_metadata = {"annotations": {"kubernetes.io/change-cause": "NOPE"}}
            data = func(name="test-pod", namespace="test", metadata=test_metadata)

            self.assertEqual(data.annotations, {"kubernetes.io/change-cause": "NOPE"})

    def test_enforce_only_strings_dict(self):
        func = getattr(kubernetes, "__enforce_only_strings_dict")
        data = {
            "unicode": 1,
            2: 2,
        }
        self.assertEqual(
            {"unicode": "1", "2": "2"},
            func(data),
        )
