from typing import Any
from typing import Union
from typing import Tuple
import typing


def _typing_generic_converter(T: Any, *, recursive: bool = True) -> Union[type, Tuple[Any, ...]]:
    """Simplifies special types for an :func:`isinstance` check to work.
    Converts ``typing.Union[A, B, C, ...]`` into the tuple ``(A, B, C, ...)``;
    ``typing.Optional[T]`` into the tuple ``(T, NoneType)``;
    and any subscripted generic type (in the form ``C[T]``) into the original, non-subscripted class
    (``C``).
    If the given type is not one of the aforementioned special types, then it is returned unchanged.

    Args:
        T (:class:`type`): Special type to simplify (see above).
        recursive (:class:`bool`, optional): If ``True``, then, if the given type (arg ``T``) is a Union or Optional,
            applies this function recursively on the elements of the resulting tuple of arguments, simplifying any
            further generic subscriptions. Defaults to ``True``. (If ``False``, resulting tuple elements can't be
            guaranteed to be types, as they can be generic subscriptions instead.)

    Returns:
        Union[:class:`type`, Tuple[Any, ...]]: The simplified type(s). If the given type was not
        :class:`typing.Union`, :class:`typing.Optional` or a subscripted generic type, then it is returned
        unchanged.

    Examples:
        .. testsetup:: *

            import typing
            from serpcord.utils.model import _typing_generic_converter
        .. doctest::

            >>> _typing_generic_converter(typing.Union[str, int, bool])  # str, int, or bool, as a tuple
            (<class 'str'>, <class 'int'>, <class 'bool'>)
            >>> _typing_generic_converter(typing.Optional[dict])  # dict or None, as a tuple
            (<class 'dict'>, <class 'NoneType'>)
            >>> S = typing.TypeVar("S")
            >>> class ExampleGenericType(typing.Generic[S]):
            ...     pass
            >>> _typing_generic_converter(ExampleGenericType[str])  # removes subscription
            <class 'ExampleGenericType'>
            >>> _typing_generic_converter(typing.Union[ExampleGenericType[str], int])  # recursive simplification
            (<class 'ExampleGenericType'>, <class 'int'>)
            >>> _typing_generic_converter(typing.Union[ExampleGenericType[str], int], recursive=False)
            (ExampleGenericType[str], <class 'int'>)
    """
    gen_alias = getattr(typing, "_GenericAlias", None)
    origin_type = getattr(T, "__origin__", None)
    if origin_type == typing.Union:
        resulting_tuple: Tuple[type, ...] = getattr(T, "__args__", tuple())  # Union[A, B, C] -> (A, B, C)
        if recursive and isinstance(resulting_tuple, tuple):  # Union[C[T], D[C[Z]]] -> (C, D)
            return typing.cast(
                Tuple[type],
                tuple(_typing_generic_converter(T_, recursive=True) for T_ in resulting_tuple)
            )
            # note: Union[Union[Union[...]]] is already automatically simplified by the typing module, so no worries
            # about nested tuples.
        else:
            return resulting_tuple
    elif isinstance(origin_type, type) and isinstance(gen_alias, type) and isinstance(T, gen_alias):
        return origin_type  # C[T] -> C
    else:
        return T