from __future__ import annotations

from typing import Iterator, Sequence, cast, overload

from cognite.client._api_client import APIClient
from cognite.client._constants import DATA_MODELING_LIST_LIMIT_DEFAULT
from cognite.client.data_classes.data_modeling.data_models import (
    DataModel,
    DataModelApply,
    DataModelFilter,
    DataModelList,
)
from cognite.client.data_classes.data_modeling.ids import DataModelIdentifier, VersionedDataModelingId, load_identifier


class DataModelsAPI(APIClient):
    _RESOURCE_PATH = "/models/datamodels"

    @overload
    def __call__(
        self,
        chunk_size: None = None,
        limit: int = None,
        space: str | None = None,
        inline_views: bool = False,
        all_versions: bool = False,
        include_global: bool = False,
    ) -> Iterator[DataModel]:
        ...

    @overload
    def __call__(
        self,
        chunk_size: int,
        limit: int = None,
        space: str | None = None,
        inline_views: bool = False,
        all_versions: bool = False,
        include_global: bool = False,
    ) -> Iterator[DataModelList]:
        ...

    def __call__(
        self,
        chunk_size: int | None = None,
        limit: int = None,
        space: str | None = None,
        inline_views: bool = False,
        all_versions: bool = False,
        include_global: bool = False,
    ) -> Iterator[DataModel] | Iterator[DataModelList]:
        """Iterate over data model

        Fetches data model as they are iterated over, so you keep a limited number of data model in memory.

        Args:
            chunk_size (int, optional): Number of data model to return in each chunk. Defaults to yielding one data_model a time.
            limit (int, optional): Maximum number of data model to return. Default to return all items.
            space: (str | None): The space to query.
            inline_views (bool): Whether to expand the referenced views inline in the returned result.
            all_versions (bool): Whether to return all versions. If false, only the newest version is returned,
                                 which is determined based on the 'createdTime' field.
            include_global (bool): Whether to include global views.

        Yields:
            Union[DataModel, DataModelList]: yields DataModel one by one if chunk_size is not specified, else DataModelList objects.
        """
        filter = DataModelFilter(space, inline_views, all_versions, include_global)

        return self._list_generator(
            list_cls=DataModelList,
            resource_cls=DataModel,
            method="GET",
            chunk_size=chunk_size,
            limit=limit,
            filter=filter.dump(camel_case=True),
        )

    def __iter__(self) -> Iterator[DataModel]:
        """Iterate over data model

        Fetches data model as they are iterated over, so you keep a limited number of data model in memory.

        Yields:
            DataModel: yields DataModels one by one.
        """
        return cast(Iterator[DataModel], self())

    def retrieve(self, ids: DataModelIdentifier | Sequence[DataModelIdentifier]) -> DataModelList:
        """`Retrieve data_model(s) by id(s). <https://docs.cognite.com/api/v1/#tag/Data-models/operation/byExternalIdsDataModels>`_

        Args:
            ids (DataModelId | Sequence[DataModelId]): Data Model identifier(s).

        Returns:
            Optional[DataModel]: Requested data_model or None if it does not exist.

        Examples:

                >>> from cognite.client import CogniteClient
                >>> c = CogniteClient()
                >>> res = c.data_modeling.data_models.retrieve(("mySpace", "myDataModel", "v1"))

        """
        identifier = load_identifier(ids, "data_model")
        return self._retrieve_multiple(list_cls=DataModelList, resource_cls=DataModel, identifiers=identifier)

    def delete(self, ids: DataModelIdentifier | Sequence[DataModelIdentifier]) -> list[VersionedDataModelingId]:
        """`Delete one or more data model <https://docs.cognite.com/api/v1/#tag/Data-models/operation/deleteDataModels>`_

        Args:
            ids (DataModelId | Sequence[DataModelId]): Data Model identifier(s).
        Returns:
            The data_model(s) which has been deleted. None if nothing was deleted.
        Examples:

            Delete data model by id::

                >>> from cognite.client import CogniteClient
                >>> c = CogniteClient()
                >>> c.data_modeling.data_models.delete(("mySpace", "myDataModel", "v1"))
        """
        deleted_data_models = cast(
            list,
            self._delete_multiple(identifiers=load_identifier(ids, "data_model"), wrap_ids=True, returns_items=True),
        )
        return [
            VersionedDataModelingId(item["space"], item["externalId"], item["version"]) for item in deleted_data_models
        ]

    def list(
        self,
        limit: int = DATA_MODELING_LIST_LIMIT_DEFAULT,
        space: str | None = None,
        inline_views: bool = False,
        all_versions: bool = False,
        include_global: bool = False,
    ) -> DataModelList:
        """`List data models <https://docs.cognite.com/api/v1/#tag/Data-models/operation/listDataModels>`_

        Args:
            limit (int, optional): Maximum number of data model to return. Default to 10. Set to -1, float("inf") or None
                to return all items.
            space: (str | None): The space to query.
            inline_views (bool): Whether to expand the referenced views inline in the returned result.
            all_versions (bool): Whether to return all versions. If false, only the newest version is returned,
                                 which is determined based on the 'createdTime' field.
            include_global (bool): Whether to include global data models.

        Returns:
            DataModelList: List of requested data model

        Examples:

            List 5 data model:

                >>> from cognite.client import CogniteClient
                >>> c = CogniteClient()
                >>> data_model_list = c.data_modeling.data_models.list(limit=5)

            Iterate over data model:

                >>> from cognite.client import CogniteClient
                >>> c = CogniteClient()
                >>> for data_model in c.data_modeling.data_models:
                ...     data_model # do something with the data_model

            Iterate over chunks of data model to reduce memory load:

                >>> from cognite.client import CogniteClient
                >>> c = CogniteClient()
                >>> for data_model_list in c.data_modeling.data_models(chunk_size=10):
                ...     data_model_list # do something with the data model
        """
        filter = DataModelFilter(space, inline_views, all_versions, include_global)

        return self._list(
            list_cls=DataModelList,
            resource_cls=DataModel,
            method="GET",
            limit=limit,
            filter=filter.dump(camel_case=True),
        )

    @overload
    def apply(self, data_model: Sequence[DataModelApply]) -> DataModelList:
        ...

    @overload
    def apply(self, data_model: DataModelApply) -> DataModel:
        ...

    def apply(self, data_model: DataModelApply | Sequence[DataModelApply]) -> DataModel | DataModelList:
        """`Create or update one or more data model. <https://docs.cognite.com/api/v1/#tag/Data-models/operation/createDataModels>`_

        Args:
            data_model (data_model: DataModelApply | Sequence[DataModelApply]): DataModel or data model to create or update (upsert).

        Returns:
            DataModel | DataModelList: Created data_model(s)

        Examples:

            Create new data model::

                >>> from cognite.client import CogniteClient
                >>> import cognite.client.data_classes.data_modeling as models
                >>> c = CogniteClient()
                >>> data_models = [models.DataModel(space="mySpace",external_id="myDataModel",version="v1",is_global=,last_updated_time=),
                ... DataModel(space="mySpace",external_id="myOtherDataModel",version="v1",is_global=,last_updated_time=)]
                >>> res = c.data_modeling.data_models.create(data_models)
        """
        return self._create_multiple(list_cls=DataModelList, resource_cls=DataModel, items=data_model)
