from enum import Enum
import inspect
from types import GenericAlias, ModuleType, UnionType
from typing import Any, get_args

import dynamix_sdk.types as sdk_types
import dynamix_sdk.api._nested.params as nested_params
import dynamix_sdk.api._nested.result as nested_result
import dynamix_sdk.api._nested.enums as nested_enums

from tests.conftest import SDKFunction


def check_model_field_annotation(
    annotation: Any,
    field_descr: str,
    valid_nested_model_cls: type[sdk_types.BaseModel],
):
    if not annotation:
        raise TypeError

    if annotation is Any:
        return

    assert annotation is not list, (
        f'{field_descr}: missing list elements type annotation.'
    )

    if isinstance(annotation, (UnionType, GenericAlias)):
        for annotation in get_args(annotation):
            check_model_field_annotation(
                annotation=annotation,
                field_descr=field_descr,
                valid_nested_model_cls=valid_nested_model_cls,
            )
    elif issubclass(annotation, sdk_types.BaseModel):
        model_cls = annotation
        assert issubclass(model_cls, valid_nested_model_cls), (
            f'{field_descr}: nested model class must be'
            f' a subclass of {valid_nested_model_cls.__qualname__}.'
        )

        check_model_field_annotations(
            model_cls=model_cls,
            valid_nested_model_cls=valid_nested_model_cls,
        )


def check_model_field_annotations(
    model_cls: type[sdk_types.BaseModel],
    valid_nested_model_cls: type[sdk_types.BaseModel],
):
    for field_name, field_info in model_cls.model_fields.items():
        field_descr = f'{model_cls.__qualname__}.{field_name}'

        check_model_field_annotation(
            annotation=field_info.annotation,
            field_descr=field_descr,
            valid_nested_model_cls=valid_nested_model_cls,
        )


def test_params_model_fields_type(sdk_dx_functions: tuple[SDKFunction, ...]):
    for sdk_func in sdk_dx_functions:
        params = inspect.signature(sdk_func.proto_method).parameters
        for param_name, param in params.items():
            if param_name == 'self':
                continue

            field_descr = (
                f'{sdk_func.proto_cls.__qualname__}.'
                f'{sdk_func.call_attrs[-1]}.'
                f'{param_name}'
            )

            assert param.annotation is not inspect.Parameter.empty, (
                f'{field_descr}: missing type annotation.'
            )

            assert param.kind == param.KEYWORD_ONLY, (
                f'Parameter {field_descr} must be keyword-only.'
            )

            check_model_field_annotation(
                annotation=param.annotation,
                field_descr=field_descr,
                valid_nested_model_cls=sdk_types.BaseAPIParamsNestedModel,
            )


def test_function_return_type(sdk_dx_functions):
    for sdk_func in sdk_dx_functions:
        assert issubclass(sdk_func.result_cls, sdk_types.BaseAPIResult), (
            f'Return type for method'
            f' {sdk_func.proto_cls.__qualname__}.{sdk_func.call_attrs[-1]}'
            f' must be a subclass of BaseAPIResult.'
        )


def test_result_model_fields_type():
    valid_nested_model_cls = sdk_types.BaseAPIResultNestedModel

    for subcls in sdk_types.BaseAPIResultModel.__subclasses__():
        check_model_field_annotations(
            model_cls=subcls,
            valid_nested_model_cls=valid_nested_model_cls,
        )
    for subcls in sdk_types.BaseAPIResultNestedModel.__subclasses__():
        check_model_field_annotations(
            model_cls=subcls,
            valid_nested_model_cls=valid_nested_model_cls,
        )


def check_class_inheritance_in_module(
    module: ModuleType,
    valid_base_cls: type,
):
    for attr_name in dir(module):
        if attr_name.startswith('_'):
            continue

        attr = getattr(module, attr_name)

        if not isinstance(attr, type):
            continue

        nested_result_cls = attr

        assert issubclass(nested_result_cls, valid_base_cls), (
            f'Class {nested_result_cls.__qualname__}:'
            f' must be a subclass of class {valid_base_cls.__qualname__}.'
        )


def test_class_inheritance_in_nested_result_module():
    check_class_inheritance_in_module(
        module=nested_result,
        valid_base_cls=sdk_types.BaseAPIResultNestedModel,
    )


def test_class_inheritance_in_nested_params_module():
    check_class_inheritance_in_module(
        module=nested_params,
        valid_base_cls=sdk_types.BaseAPIParamsNestedModel,
    )


def test_class_inheritance_in_nested_enums_module():
    check_class_inheritance_in_module(
        module=nested_enums,
        valid_base_cls=Enum,
    )
