"""Type inference."""

import inspect
from collections.abc import Callable
from types import UnionType
from typing import Annotated, Any, Union, get_args, get_origin

from pydantic import BaseModel

from fastlife.domain.model.template import InlineTemplate


def is_newtype(typ: type[Any]) -> bool:
    """Check tha a type is a typing.NewType"""
    return callable(typ) and hasattr(typ, "__supertype__")


def get_runtime_type(typ: type[Any]) -> type[Any]:
    """
    Unwrap the NewType if exists and return the wrapped type in that case.
    """
    if is_newtype(typ):
        typ = typ.__supertype__
    return typ


def get_type_by_discriminator(
    discriminant: str | int, discriminator: str, typ: type[Any]
) -> type[Any] | None:
    """
    Find a type from a union using a discriminator.

    :param discriminant: the value searched in the discriminator attribute.
    :param discriminator: the type attribute name, must be a literal.
    :param typ: a union type
    :return: The type of the union or None
    """
    for child_typ in get_args(typ):
        if discriminant in child_typ.model_fields[discriminator].annotation.__args__:
            if get_origin(child_typ) is Annotated:
                child_typ = get_args(child_typ)[0]
            return child_typ
    return None


def is_complex_type(typ: type[Any]) -> bool:
    """
    Used to detect complex type such as Mapping, Sequence and pydantic BaseModel.

    This method cannot be used outside pydantic serialization.
    """
    typ = get_runtime_type(typ)
    return bool(get_origin(typ) or issubclass(typ, BaseModel))


def is_union(typ: type[Any]) -> bool:
    """Used to detect unions like Optional[T], Union[T, U] or T | U."""
    type_origin = get_origin(get_runtime_type(typ))
    if type_origin:
        if type_origin is Union:  # Optional[T]
            return True

        if type_origin is UnionType:  # T | U
            return True
    return False


def is_inline_template_returned(endpoint: Callable[..., Any]) -> bool:
    """Test if a view, the endpoint return a template."""
    signature = inspect.signature(endpoint)
    return_annotation = signature.return_annotation

    if isinstance(return_annotation, type) and issubclass(
        return_annotation, InlineTemplate
    ):
        return True

    if is_union(return_annotation):
        return any(
            isinstance(arg, type) and issubclass(arg, InlineTemplate)
            for arg in get_args(return_annotation)
        )

    return False
