import dataclasses
from dataclasses import is_dataclass
from types import GenericAlias, UnionType
from typing import Dict, _UnionGenericAlias, get_args

import pydash
from pydantic._internal._model_construction import ModelMetaclass
from pydantic.fields import FieldInfo
from pydantic_core.core_schema import ModelField
from pydash import some

from .clone_generation import CloneGeneration
from .code_generation import (Generation, GenerationClassDetail, GenerationImportModuleDetail, GenerationMethodDetail,
                              GenerationPropertyDetail)
from .helper import get_generic_type, oneline
from .models.method_detail import MethodDetail


class SdkTemplate:
    def get_query_param_type(self, param_type):
        if '|' in str(param_type):
            return param_type

        return param_type.__name__

    def generate_method(self, method_detail: MethodDetail):
        params: list[ModelField] = list(method_detail.query_params + method_detail.body_params)
        params.sort(key=lambda param: method_detail.parameters_position.index(param.name))

        return GenerationMethodDetail(
            name=method_detail.function_name,
            response=self.get_response(method_detail),
            body=self.get_method_body(method_detail),
            params=list(map(lambda param: GenerationPropertyDetail(param.name, self.get_query_param_type(param.type_)), params)),
        )

    def get_method_body(self, method_detail: MethodDetail):
        body_params = [f"'{method_detail.path}'"]

        if method_detail.query_params:
            query_params = ', '.join(map(lambda query: f"'{query.name}': {query.name}", method_detail.query_params))
            body_params.append('{' + query_params + '}')
        else:
            body_params.append('None')

        if method_detail.body_params:
            body_params.append(method_detail.body_params[0].name)
        else:
            body_params.append('None')

        return f'return await self.send({", ".join(body_params)}, {self.get_response(method_detail)}, option )'

    def get_response(self, method_detail: MethodDetail):
        if not method_detail.response_field:
            return 'None'
        response_name = method_detail.response_field.type_.__name__
        if isinstance(method_detail.response_field.type_, GenericAlias):
            return get_generic_type(method_detail.response_field.type_)
        return response_name

    def generate_class(self, name, methods: list[MethodDetail]):
        class_detail = GenerationClassDetail(class_name=name, parent_name='BaseMicroserviceClient')
        for method in methods:
            class_detail.methods.append(self.generate_method(method))
        return Generation().generate(class_detail)

    def generate_ref_class(self, microservice: Dict):
        list_ref_class = list()
        list_import_module: list[GenerationImportModuleDetail] = list(
            (
                GenerationImportModuleDetail('pydantic', 'BaseModel'),
                GenerationImportModuleDetail('pydantic', 'Field'),
                GenerationImportModuleDetail('dataclasses', 'dataclass'),
                GenerationImportModuleDetail('dataclasses', 'field'),
                GenerationImportModuleDetail('enum', 'Enum'),
                GenerationImportModuleDetail('enum', 'IntEnum'),
                GenerationImportModuleDetail('typing', 'Any'),
                GenerationImportModuleDetail('typing', 'Optional'),
            )
        )
        for key in microservice.keys():
            methods: list[MethodDetail] = microservice[key]
            for method in methods:
                for body in method.body_params:
                    self.get_list_ref_class(body.type_, list_ref_class, list_import_module)
                for query in method.query_params:
                    self.get_list_ref_class(query.type_, list_ref_class, list_import_module)
                if method.response_field:
                    self.get_list_ref_class(method.response_field.type_, list_ref_class, list_import_module)

        content = oneline(Generation().generate_import_modules(list_import_module))
        list_ref_class = pydash.filter_(list_ref_class, lambda ref_class: not isinstance(ref_class, GenericAlias))

        for ref_class in list_ref_class:
            content += CloneGeneration().clone_class_to_dataclass(ref_class)
        return content

    def get_list_ref_class(
        self, ref_type, list_ref_class: list, list_import_module: list[GenerationImportModuleDetail]
    ):
        if type(ref_type) is UnionType:
            for child_type in get_args(ref_type):
                self.get_list_ref_class(child_type, list_ref_class, list_import_module)
            return
        if type(ref_type) is _UnionGenericAlias:
            for child_type in get_args(ref_type):
                self.get_list_ref_class(child_type, list_ref_class, list_import_module)
            return
        if some(
            list_import_module,
            lambda import_module: import_module.type == ref_type.__qualname__
            and import_module.module == ref_type.__module__,
        ):
            return
        if type(ref_type) is type and not is_dataclass(ref_type):
            if ref_type.__module__ != 'builtins':
                list_import_module.append(GenerationImportModuleDetail(ref_type.__module__, ref_type.__qualname__))
            return
        if ref_type in list_ref_class:
            return
        list_ref_class.append(ref_type)

        if type(ref_type) is ModelMetaclass:
            for key in ref_type.__fields__.keys():
                property: FieldInfo = ref_type.__fields__[key]
                self.get_list_ref_class(property.annotation, list_ref_class, list_import_module)
        elif is_dataclass(ref_type):
            for field in dataclasses.fields(ref_type):
                self.get_list_ref_class(field.type, list_ref_class, list_import_module)
        elif isinstance(ref_type, GenericAlias):
            for alias in ref_type.__args__:
                self.get_list_ref_class(alias, list_ref_class, list_import_module)
