# from aas_middleware.formatting.aas_client import (
#     get_aas_from_server,
#     get_all_aas_from_server,
# )
# from aas_middleware.util.convert_util import get_vars
# from aas_middleware.models import base
# from aas_middleware.util import convert_util
# from aas_middleware.util.convert_util import (
#     get_all_submodel_elements_from_submodel,
#     get_all_submodels_from_model,
#     get_vars,
# )
# from aas_middleware.util.client_utils import check_aas_and_sm_server_online

# import typing

# from fastapi import APIRouter
# from pydantic import BaseModel, create_model
# from pydantic.fields import FieldInfo

# from graphene_pydantic import PydanticObjectType, PydanticInputObjectType
# from graphene_pydantic.registry import get_global_registry

# import graphene
# # TODO: add starlette_graphene3 as depndency
# from aas_middleware.model.util import get_value_attributes
# from starlette_graphene3 import (
#     GraphQLApp,
#     make_graphiql_handler,
#     make_playground_handler,
# )

# from aas_middleware.model.formatting.aas.aas_middleware_util import get_contained_models_attribute_info, union_type_check
# from aas_middleware.model.formatting.aas.aas_model import AAS, Submodel, SubmodelElementCollection


# # TODO: update graphql router similar to rest router.



# def add_class_method(model: typing.Type):
#     def is_type_of(cls, root, info):
#         return isinstance(root, (cls, model))

#     class_method = classmethod(is_type_of)
#     model.is_type_of = class_method


# model_name_registry = set()


# def create_graphe_pydantic_output_type_for_model(
#     input_model: typing.Type[BaseModel], union_type: bool = False
# ) -> PydanticObjectType:
#     """
#     Creates a pydantic model for the given pydantic model.

#     Args:
#         model (Type[BaseModel]): Pydantic model for which the Graphene Object Type should be created.

#     Returns:
#         PydanticObjectType: Graphene Object type for the given pydantic model.
#     """
#     graphene_model_registry = get_global_registry(PydanticObjectType)._registry
#     for model in graphene_model_registry.keys():
#         if input_model == model.__name__:
#             return graphene_model_registry[model]

#     rework_default_list_to_default_factory(input_model)
#     graphene_model = type(
#         input_model.__name__,
#         (PydanticObjectType,),
#         {"Meta": type("Meta", (), {"model": input_model})},
#     )
#     if union_type:
#         add_class_method(graphene_model)

#     return graphene_model


# def is_typing_list_or_tuple(input_type: typing.Any) -> bool:
#     """
#     Checks if the given type is a typing.List or typing.Tuple.

#     Args:
#         input_type (typing.Any): Type to check.

#     Returns:
#         bool: True if the given type is a typing.List or typing.Tuple, False otherwise.
#     """
#     return hasattr(input_type, "__origin__") and (
#         input_type.__origin__ == list or input_type.__origin__ == tuple
#     )


# def list_contains_any_submodel_element_collections(
#     input_type: typing.Union[typing.List, typing.Tuple]
# ) -> bool:
#     return any(
#         issubclass(input_type.__origin__, base.SubmodelElementCollection)
#         for nested_types in input_type.__args__
#     )


# def rework_default_list_to_default_factory(model: BaseModel):
#     for names, field in model.__fields__.items():
#         if field.default:
#             pass
#         if (
#             isinstance(field.default, list)
#             or isinstance(field.default, tuple)
#             or isinstance(field.default, set)
#         ):
#             if field.default:
#                 field.type_ = type(field.default[0])
#                 field.outer_type_ = typing.List[type(field.default[0])]
#             else:
#                 field.type_ = str
#                 field.outer_type_ = typing.List[str]
#             field.default = None
#             field.field_info = FieldInfo(extra={})
#             field.required = True
#         if isinstance(field.default, BaseModel):
#             field.default = None
#             field.field_info = FieldInfo(extra={})
#             field.required = True


# def create_graphe_pydantic_output_type_for_submodel_elements(
#     model: Submodel, union_type: bool = False
# ) -> PydanticObjectType:
#     """
#     Create recursively graphene pydantic output types for submodels and submodel elements.

#     Args:
#         model (typing.Union[base.Submodel, base.SubmodelElementCollectiontuple, list, set, ]): Submodel element for which the graphene pydantic output types should be created.
#     """
#     # TODO: use here model_fields
#     for attribute_name, attribute_value in get_all_submodel_elements_from_submodel(
#         model
#     ).items():
#         if union_type_check(attribute_value):
#             subtypes = typing.get_args(attribute_value)
#             for subtype in subtypes:
#                 create_graphe_pydantic_output_type_for_submodel_elements(
#                     subtype, union_type=True
#                 )
#         elif hasattr(attribute_value, "__fields__") and issubclass(
#             attribute_value, SubmodelElementCollection
#         ):
#             create_graphe_pydantic_output_type_for_submodel_elements(attribute_value)
#         elif is_typing_list_or_tuple(attribute_value):
#             if list_contains_any_submodel_element_collections(attribute_value):
#                 for nested_type in attribute_value.__args__:
#                     if union_type_check(nested_type):
#                         subtypes = typing.get_args(nested_type)
#                         for subtype in subtypes:
#                             create_graphe_pydantic_output_type_for_submodel_elements(
#                                 subtype, union_type=True
#                             )
#                     elif issubclass(nested_type, SubmodelElementCollection):
#                         create_graphe_pydantic_output_type_for_submodel_elements(
#                             nested_type
#                         )
#     return create_graphe_pydantic_output_type_for_model(model, union_type)


# def get_base_query_and_mutation_classes() -> (
#     typing.Tuple[graphene.ObjectType, graphene.ObjectType]
# ):
#     """
#     Returns the base query and mutation classes for the GraphQL endpoint.

#     Returns:
#         tuple: Tuple of the base query and mutation classes for the GraphQL endpoint.
#     """

#     class Query(graphene.ObjectType):
#         pass

#     class Mutation(graphene.ObjectType):
#         pass

#     return Query, Mutation


# def get_aas_resolve_function(model: typing.Type[BaseModel]) -> typing.Callable:
#     """
#     Returns the resolve function for the given pydantic model.

#     Args:
#         model (Type[BaseModel]): Pydantic model for which the resolve function should be created.

#     Returns:
#         typing.Callable: Resolve function for the given pydantic model.
#     """
#     async def resolve_models(self, info):
#         # TODO: use here connectors
#         await check_aas_and_sm_server_online()
#         data_retrieved = await get_all_aas_from_server(model)
#         return data_retrieved

#     resolve_models.__name__ = f"resolve_{model.__name__}"
#     return resolve_models


# def get_submodel_resolve_function(model: Type[BaseModel]) -> typing.Callable:
#     """
#     Returns the resolve function for the given pydantic model.

#     Args:
#         model (Type[BaseModel]): Pydantic model for which the resolve function should be created.

#     Returns:
#         typing.Callable: Resolve function for the given pydantic model.
#     """

#     async def resolve_models(self, info):
#         # TODO: use here connectors
#         await check_aas_and_sm_server_online()
#         data_retrieved = await get_all_submodels_of_type(model)
#         return data_retrieved

#     resolve_models.__name__ = f"resolve_{model.__name__}"
#     return resolve_models


# def generate_graphql_endpoint(models: typing.List[typing.Type[BaseModel]]) -> GraphQLApp:
#     """
#     Generates a GraphQL endpoint for the given pydantic models.
#     Args:
#         models (List[Type[BaseModel]]): List of pydantic models.
#     Returns:
#         APIRouter: FastAPI router with GraphQL endpoint for the given pydantic models.
#     """
#     query, mutation = get_base_query_and_mutation_classes()
#     # TODO: also make mutation possible
#     # TODO: implement this function for data models.
#     for model in models:
#         model_name = model.__name__

#         submodels = get_contained_models_attribute_info(model)
#         graphene_submodels = []
#         for submodel in submodels:
#             graphene_submodels.append(
#                 create_graphe_pydantic_output_type_for_submodel_elements(submodel)
#             )

#         for submodel, graphene_submodel in zip(submodels, graphene_submodels):
#             submodel_name = submodel.__name__
#             class_dict = {
#                 f"{submodel_name}": graphene.List(graphene_submodel),
#                 f"resolve_{submodel_name}": get_submodel_resolve_function(submodel),
#             }
#             query = type("Query", (query,), class_dict)

#     for model in models:

#         graphene_model = create_graphe_pydantic_output_type_for_model(model)

#         class_dict = {
#             f"{model_name}": graphene.List(graphene_model),
#             f"resolve_{model_name}": get_aas_resolve_function(model),
#         }
#         query = type("Query", (query,), class_dict)

#     schema = graphene.Schema(query=query)
#     return GraphQLApp(schema=schema, on_get=make_graphiql_handler())
