import inspect
import typing
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Type, TypedDict, Union, get_origin

from messageprocessor import MessageProcessor
from simplehttprouter import Route

from ...model import InfoModel
from ...utils import (
    get_inner_optional_type,
    get_type_params,
    is_optional,
    is_typing_alias,
    is_union
)


class SchemaConflictStrategy(str, Enum):
    reuse = "reuse"
    uuid = "uuid"
    readable = "readable"


class BuilderConfig(TypedDict, total=False):
    info_data: InfoModel
    conflict_strategy: SchemaConflictStrategy
    ref_array_types: bool


class ExtraMappingDef(TypedDict):
    name: str
    location: str


class RouteBuildParams(TypedDict):
    route: Route
    processor_type: Optional[Type[MessageProcessor]]
    processor_instance: Optional[MessageProcessor]
    interactor_type: Optional[Type]
    interactor_instance: Optional[Any]
    interactor_method_name: Optional[str]
    request_type: Optional[Type]
    response_type: Optional[Type]
    success_status: Optional[int]
    request_content_type: Optional[str]
    response_content_type: Optional[str]
    exception_map: Optional[Dict[Type, Union[int, Tuple[int, str]]]]
    extra_mapping: Optional[Dict[str, ExtraMappingDef]]


def build_params(
    route: Route,
    processor_type: Optional[Type[MessageProcessor]] = None,
    processor_instance: Optional[MessageProcessor] = None,
    interactor_type: Optional[Type] = None,
    interactor_instance: Optional[Any] = None,
    interactor_method_name: Optional[str] = None,
    request_type: Optional[Type] = None,
    response_type: Optional[Type] = None,
    success_status: Optional[int] = None,
    request_content_type: Optional[str] = None,
    response_content_type: Optional[str] = None,
    exception_map: Optional[Dict[Type, Union[int, Tuple[int, str]]]] = None,
    extra_mapping: Optional[Dict[str, ExtraMappingDef]] = None,
) -> RouteBuildParams:
    return {
        "route": route,
        "processor_type": processor_type,
        "processor_instance": processor_instance,
        "interactor_type": interactor_type,
        "interactor_instance": interactor_instance,
        "interactor_method_name": interactor_method_name,
        "request_type": request_type,
        "response_type": response_type,
        "success_status": success_status,
        "request_content_type": request_content_type,
        "response_content_type": response_content_type,
        "exception_map": exception_map,
        "extra_mapping": extra_mapping,
    }


class TypeParameterDef(TypedDict):
    name: str
    type: type
    optional: bool
    default: Any


class TypeDefinition(TypedDict):
    origin_type: Optional[type]
    subtypes: Optional[List[Optional[type]]]
    optional: bool
    parameters: List[TypeParameterDef]


def _resolve_type_hints(type_param) -> Dict[str, Any]:
    """Resolve forward references in type annotations using typing.get_type_hints()."""
    try:
        if hasattr(type_param, "__init__"):
            return typing.get_type_hints(type_param.__init__)
        return typing.get_type_hints(type_param)
    except Exception:
        return {}


def get_type_parameters(type_param) -> List[TypeParameterDef]:
    keys_to_ignore = []
    if hasattr(type_param, "_real_cls"):
        _cls_attrs = getattr(type_param, "_cls_attrs")
        if _cls_attrs is not None:
            keys_to_ignore = _cls_attrs.keys()
        type_param = getattr(type_param, "_real_cls")

    signature = None
    if hasattr(type_param, "__init__"):
        signature = inspect.signature(type_param.__init__)

    resolved_hints = _resolve_type_hints(type_param)

    ret = []
    if signature is not None:
        for parameter in signature.parameters.values():
            if parameter.name in ["self", "args", "kwargs"]:
                continue

            if parameter.name in keys_to_ignore:
                continue

            param_type = resolved_hints.get(parameter.name, parameter.annotation)

            parameter_def: TypeParameterDef = {
                "name": parameter.name,
                "type": param_type,
                "optional": is_optional(type_param) or parameter.default != inspect.Signature.empty,
                "default": parameter.default,
            }
            ret.append(parameter_def)
    return ret


def get_type_definition(type_param) -> TypeDefinition:
    if type_param is None:
        return {
            "origin_type": None,
            "subtypes": None,
            "optional": True,
            "parameters": [],
        }
    origin_type = type_param
    type_params = None
    if is_typing_alias(type_param):
        if is_optional(type_param):
            result = get_type_definition(get_inner_optional_type(type_param))
            result["optional"] = True
            return result
        if is_union(type_param):
            for inner_type in get_type_params(type_param):
                result = get_type_definition(inner_type)
                if result is not None:
                    return result
            return {
                "origin_type": None,
                "subtypes": None,
                "optional": True,
                "parameters": [],
            }

        origin_type = get_origin(type_param)
        type_params = get_type_params(type_param)

    sub_types = None
    if type_params is not None:
        sub_types = [get_type_definition(st) for st in type_params]
        sub_types = [sta["origin_type"] for sta in sub_types]

    return {
        "origin_type": origin_type,
        "subtypes": sub_types,
        "optional": False,
        "parameters": get_type_parameters(origin_type),
    }
