# ***************************************************************** #
# (C) Copyright IBM Corporation 2024.                               #
#                                                                   #
# The source code for this program is not published or otherwise    #
# divested of its trade secrets, irrespective of what has been      #
# deposited with the U.S. Copyright Office.                         #
# ***************************************************************** #
# SPDX-License-Identifier: Apache-2.0
"""This module contains utility functions used throughout kubernetes_lite
testing
"""

import copy
from contextlib import nullcontext

from kubernetes_lite.client import DynamicResource
from kubernetes_lite.errors import NotFoundError

import pytest
from deepdiff import DeepDiff

DEFAULT_OBJ_NAME = "test-deployment"
OBJ_KIND = "Deployment"
OBJ_API_VERSION = "apps/v1"


def generate_test_object(namespace: str, name: str = DEFAULT_OBJ_NAME) -> dict:
    """Create a generic kube Deployment to be used for testing

    Args:
        namespace (str): The namespace the resource should be in
        name (str, optional): The name of the object. Defaults to DEFAULT_OBJ_NAME.

    Returns:
        dict: A example deployment object
    """
    return {
        "apiVersion": OBJ_API_VERSION,
        "kind": OBJ_KIND,
        "metadata": {"name": name, "namespace": namespace, "labels": {"app": "test"}},
        "spec": {
            "replicas": 3,
            "selector": {"matchLabels": {"app": "test"}},
            "template": {
                "metadata": {"labels": {"app": "test"}},
                "spec": {"containers": [{"name": "test", "image": "test:1", "ports": [{"containerPort": 80}]}]},
            },
        },
    }


def clean_object_metadata(obj: dict) -> dict:
    """Duplicate an object and remove metadata that changes on every
    request

    Args:
        obj (dict): The object to clean

    Returns:
        dict: A cleaned object
    """
    copied_obj = copy.deepcopy(obj)
    copied_obj["metadata"].pop("resourceVersion", None)
    copied_obj["metadata"].pop("generation", None)
    copied_obj["metadata"].pop("managedFields", None)
    copied_obj["metadata"].pop("uid", None)
    return copied_obj


def validate_result(res_obj: dict | None, expected_obj: dict | None):
    """Validate that two objects are equal

    Args:
        res_obj (dict | None): The result object
        expected_obj (dict | None): The expected object it should match
    """
    assert res_obj
    assert isinstance(res_obj, dict)
    assert expected_obj
    assert isinstance(expected_obj, dict)

    res_obj = clean_object_metadata(res_obj)
    expected_obj = clean_object_metadata(expected_obj)

    diff = DeepDiff(expected_obj, res_obj).to_dict()
    assert len(diff.get("values_changed", [])) == 0
    assert len(diff.get("dictionary_item_removed", [])) == 0


def check_object_status(
    res: DynamicResource,
    namespace: str | None = None,
    name: str = DEFAULT_OBJ_NAME,
    exists: bool = True,
    expected_obj: dict | None = None,
):
    """Check that the object generated by generate_test_object has a certain state in the cluster

    Args:
        res (DynamicResource): The dynamic resource to make requests with
        namespace (str | None, optional): Namespace the object resides in. Defaults to None.
        name (str, optional): Name of the object. Defaults to DEFAULT_OBJ_NAME.
        exists (bool, optional): If the object should exist in the cluster. Useful for delete tests. Defaults to True.
        expected_obj (dict | None, optional): What the cluster object should look like. Defaults to None.
    """
    # Get name from the object
    if not namespace and expected_obj:
        namespace = expected_obj["metadata"].get("namespace")
        name = expected_obj["metadata"].get("name")

    # If the object should exist or not
    error_catcher = nullcontext()
    if not exists:
        error_catcher = pytest.raises(NotFoundError)

    obj = None
    with error_catcher:
        obj = res.get(name=name, namespace=namespace)

    if exists:
        assert obj
        if expected_obj:
            # Remove resourceVersion since those changes do not matter to diff
            validate_result(obj, expected_obj)
    else:
        assert not obj
