from unittest.mock import Mock

import pytest
from kubernetes.dynamic.resource import ResourceInstance, Resource

from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
    K8sService,
    diff_objects,
)

from kubernetes.dynamic.exceptions import NotFoundError

pod_definition = {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "foo",
        "labels": {"environment": "production", "app": "nginx"},
        "namespace": "foo",
    },
    "spec": {
        "containers": [
            {
                "name": "nginx",
                "image": "nginx:1.14.2",
                "command": ["/bin/sh", "-c", "sleep 10"],
            }
        ]
    },
}

pod_definition_updated = {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "foo",
        "labels": {"environment": "testing", "app": "nginx"},
        "namespace": "bar",
    },
    "spec": {
        "containers": [
            {
                "name": "nginx",
                "image": "nginx:1.14.2",
                "command": ["/bin/sh", "-c", "sleep 10"],
            }
        ]
    },
}


@pytest.fixture(scope="module")
def mock_pod_resource_instance():
    return ResourceInstance(None, pod_definition)


@pytest.fixture(scope="module")
def mock_pod_updated_resource_instance():
    return ResourceInstance(None, pod_definition_updated)


def test_diff_objects_no_diff():
    match, diff = diff_objects(pod_definition, pod_definition)

    assert match is True
    assert diff == {}


def test_diff_objects_meta_diff():
    match, diff = diff_objects(pod_definition, pod_definition_updated)

    assert match is False
    assert diff["before"] == {
        "metadata": {"labels": {"environment": "production"}, "namespace": "foo"}
    }
    assert diff["after"] == {
        "metadata": {"labels": {"environment": "testing"}, "namespace": "bar"}
    }


def test_diff_objects_spec_diff():
    pod_definition_updated = {
        "apiVersion": "v1",
        "kind": "Pod",
        "metadata": {
            "name": "foo",
            "labels": {"environment": "production", "app": "nginx"},
            "namespace": "foo",
        },
        "spec": {
            "containers": [
                {
                    "name": "busybox",
                    "image": "busybox",
                    "command": ["/bin/sh", "-c", "sleep 3600"],
                }
            ]
        },
    }
    match, diff = diff_objects(pod_definition, pod_definition_updated)

    assert match is False
    assert diff["before"]["spec"] == pod_definition["spec"]
    assert diff["after"]["spec"] == pod_definition_updated["spec"]


def test_find_resource():
    mock_pod_resource = Resource(
        api_version="v1", kind="Pod", namespaced=False, preferred=True, prefix="api"
    )
    spec = {"resource.return_value": mock_pod_resource}
    client = Mock(**spec)
    svc = K8sService(client, Mock())
    resource = svc.find_resource("Pod", "v1")

    assert isinstance(resource, Resource)
    assert resource.to_dict().items() <= mock_pod_resource.to_dict().items()


def test_service_delete_existing_resource(mock_pod_resource_instance):
    spec = {"delete.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock(
        params={"delete_options": {"gracePeriodSeconds": 2}}, check_mode=False
    )
    resource = Mock()
    svc = K8sService(client, module)
    result = svc.delete(resource, pod_definition, mock_pod_resource_instance)

    assert isinstance(result, dict)
    assert result == mock_pod_resource_instance.to_dict()
    client.delete.assert_called_with(
        resource,
        name=pod_definition["metadata"]["name"],
        namespace=pod_definition["metadata"]["namespace"],
        body={"apiVersion": "v1", "kind": "DeleteOptions", "gracePeriodSeconds": 2},
    )


def test_service_delete_no_existing_resource():
    module = Mock()
    module.params = {}
    module.check_mode = False
    client = Mock()
    client.delete.return_value = mock_pod_resource_instance
    svc = K8sService(client, module)
    result = svc.delete(Mock(), pod_definition)

    assert result == {}
    client.delete.assert_not_called()


def test_service_delete_existing_resource_check_mode(mock_pod_resource_instance):
    module = Mock(params={}, check_mode=True)
    client = Mock(dry_run=False)
    client.delete.return_value = mock_pod_resource_instance
    svc = K8sService(client, module)
    result = svc.delete(Mock(), pod_definition, mock_pod_resource_instance)

    assert result == {}
    client.delete.assert_not_called()


def test_service_create_resource(mock_pod_resource_instance):
    spec = {"create.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    module.check_mode = False
    svc = K8sService(client, module)
    result = svc.create(Mock(), pod_definition)

    assert result == mock_pod_resource_instance.to_dict()


def test_service_create_resource_check_mode():
    client = Mock(dry_run=False)
    client.create.return_value = mock_pod_resource_instance
    module = Mock(params={}, check_mode=True)
    svc = K8sService(client, module)
    result = svc.create(Mock(), pod_definition)

    assert result == pod_definition
    client.create.assert_not_called()


def test_service_retrieve_existing_resource(mock_pod_resource_instance):
    spec = {"get.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    svc = K8sService(client, module)
    results = svc.retrieve(Mock(), pod_definition)

    assert isinstance(results, ResourceInstance)
    assert results.to_dict() == pod_definition


def test_service_retrieve_no_existing_resource():
    spec = {"get.side_effect": [NotFoundError(Mock())]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    svc = K8sService(client, module)
    results = svc.retrieve(Mock(), pod_definition)

    assert results is None


def test_create_project_request():
    project_definition = {
        "apiVersion": "v1",
        "kind": "ProjectRequest",
        "metadata": {"name": "test"},
    }
    spec = {"create.side_effect": [ResourceInstance(None, project_definition)]}
    client = Mock(**spec)
    module = Mock()
    module.check_mode = False
    module.params = {"state": "present"}
    svc = K8sService(client, module)
    results = svc.create_project_request(project_definition)

    assert isinstance(results, dict)
    assert results["changed"] is True
    assert results["result"] == project_definition


def test_service_apply_existing_resource(mock_pod_resource_instance):
    spec = {"apply.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {"apply": True}
    module.check_mode = False
    svc = K8sService(client, module)
    result = svc.apply(Mock(), pod_definition_updated, mock_pod_resource_instance)

    assert result == mock_pod_resource_instance.to_dict()


def test_service_replace_existing_resource(mock_pod_resource_instance):
    spec = {"replace.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    module.check_mode = False
    svc = K8sService(client, module)
    result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance)

    assert result == mock_pod_resource_instance.to_dict()


def test_service_update_existing_resource(mock_pod_resource_instance):
    spec = {"replace.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    module.check_mode = False
    svc = K8sService(client, module)
    result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance)

    assert result == mock_pod_resource_instance.to_dict()


def test_service_find(mock_pod_resource_instance):
    spec = {"get.side_effect": [mock_pod_resource_instance]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    module.check_mode = False
    svc = K8sService(client, module)
    results = svc.find("Pod", "v1", name="foo", namespace="foo")

    assert isinstance(results, dict)
    assert results["api_found"] is True
    assert results["resources"] is not []
    assert len(results["resources"]) == 1
    assert results["resources"][0] == pod_definition


def test_service_find_error():
    spec = {"get.side_effect": [NotFoundError(Mock())]}
    client = Mock(**spec)
    module = Mock()
    module.params = {}
    module.check_mode = False
    svc = K8sService(client, module)
    results = svc.find("Pod", "v1", name="foo", namespace="foo")

    assert isinstance(results, dict)
    assert results["api_found"] is True
    assert results["resources"] == []
