"""Module for enforcing type hints in Python functions."""

import inspect
import typing
import logging


def enforce_types(func):
    """
    Function decorator to enforce type hints at runtime.

    This decorator checks the types of the arguments and of the return value of
    the decorated function against the type hints specified in the function
    signature. If the types do not match, a TypeError is raised.
    Type checking is only performed when the logging level is set to `DEBUG`.

    :param Callable func: The function to be decorated.
    :return: The decorated function with enforced type hints.
    :rtype: Callable

    :Example:

    >>> @enforce_types
        def dummy_function(a: int, b: float) -> float:
        ... return a+b

        # This always works.
        dummy_function(1, 2.0)

        # This raises a TypeError for the second argument, if logging is set to
        # `DEBUG`.
        dummy_function(1, "Hello, world!")


    >>> @enforce_types
        def dummy_function2(a: int, right: bool) -> float:
        ... if right:
        ...     return float(a)
        ... else:
        ...     return "Hello, world!"

        # This always works.
        dummy_function2(1, right=True)

        # This raises a TypeError for the return value if logging is set to
        # `DEBUG`.
        dummy_function2(1, right=False)
    """

    def wrapper(*args, **kwargs):
        """
        Wrapper function to enforce type hints.

        :param tuple args: Positional arguments passed to the function.
        :param dict kwargs: Keyword arguments passed to the function.
        :raises TypeError: If the argument or return type does not match the
            specified type hints.
        :return: The result of the decorated function.
        :rtype: Any
        """
        level = logging.getLevelName(logging.getLogger().getEffectiveLevel())

        # Enforce type hints only in debug mode
        if level != "DEBUG":
            return func(*args, **kwargs)

        # Get the type hints for the function arguments
        hints = typing.get_type_hints(func)
        sig = inspect.signature(func)
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()

        for arg_name, arg_value in bound.arguments.items():
            expected_type = hints.get(arg_name)
            if expected_type and not isinstance(arg_value, expected_type):
                raise TypeError(
                    f"Argument '{arg_name}' must be {expected_type.__name__}, "
                    f"but got {type(arg_value).__name__}!"
                )

        # Get the type hints for the return values
        return_type = hints.get("return")
        result = func(*args, **kwargs)

        if return_type and not isinstance(result, return_type):
            raise TypeError(
                f"Return value must be {return_type.__name__}, "
                f"but got {type(result).__name__}!"
            )

        return result

    return wrapper
