# Copyright 2024-present, Argilla, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from datetime import datetime
from typing import Any, TYPE_CHECKING, Optional
from uuid import UUID

from argilla_sdk._exceptions import ArgillaSerializeError
from argilla_sdk._helpers._mixins import LoggingMixin, UUIDMixin

if TYPE_CHECKING:
    from argilla_sdk.client import Argilla
    from argilla_sdk._models import ResourceModel
    from argilla_sdk._api._base import ResourceAPI


class Resource(LoggingMixin, UUIDMixin):
    """Base class for all resources (Dataset, Workspace, User, etc.)"""

    _model: "ResourceModel"
    _client: "Argilla"
    _api: "ResourceAPI"

    _MAX_OUTDATED_RETENTION = 30

    def __init__(self, api: Optional["ResourceAPI"] = None, client: Optional["Argilla"] = None) -> None:
        self._client = client
        self._api = api

        self._last_api_call = None

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._model})"

    def __eq__(self, other) -> bool:
        if not isinstance(other, Resource):
            return False
        if not hasattr(other, "_model"):
            return super().__eq__(other)
        return self._model == other._model

    @property
    def id(self) -> Optional[UUID]:
        return self._model.id

    @id.setter
    def id(self, value: UUID) -> None:
        self._model.id = value

    @property
    def is_outdated(self) -> bool:
        """Checks if the resource is outdated based on the last API call
        Returns:
            bool: True if the resource is outdated, False otherwise
        """
        seconds = self._seconds_from_last_api_call()
        if seconds is None:
            return True
        return seconds > self._MAX_OUTDATED_RETENTION

    def api_model(self):
        """Returns the model that is used to interact with the API"""
        return self._model

    ############################
    # CRUD operations
    ############################

    def create(self) -> "Resource":
        response_model = self._api.create(self._model)
        self._sync(response_model)
        self._update_last_api_call()
        self.log(f"Resource created: {self}")
        return self

    def get(self) -> "Resource":
        response_model = self._api.get(self._model.id)
        self._sync(response_model)
        self._update_last_api_call()
        self.log(f"Resource fetched: {self}")
        return self

    def update(self) -> "Resource":
        response_model = self._api.update(self._model)
        self._sync(response_model)
        self._update_last_api_call()
        self.log(f"Resource updated: {self}")
        return self

    def delete(self) -> None:
        self._api.delete(self._model.id)
        self._update_last_api_call()
        self.log(f"Resource deleted: {self}")

    ############################
    # Serialization
    ############################

    def serialize(self) -> dict[str, Any]:
        try:
            return self._model.model_dump()
        except Exception as e:
            raise ArgillaSerializeError(f"Failed to serialize the resource. {e.__class__.__name__}") from e

    def serialize_json(self) -> str:
        try:
            return self._model.model_dump_json()
        except Exception as e:
            raise ArgillaSerializeError(f"Failed to serialize the resource. {e.__class__.__name__}") from e

    def _sync(self, model: "ResourceModel"):
        """Updates the resource with the ClientAPI that is used to interact with
        Argilla and adds an updated model to the resource.
        Args:
            model (Union[WorkspaceModel, UserModel, DatasetModel]): The updated model
        Returns:
            Self: The updated resource
        """
        self._model = model
        # set all attributes from the model to the resource
        for field in self._model.model_fields:
            setattr(self, field, getattr(self._model, field))
        return self

    def _update_last_api_call(self):
        self._last_api_call = datetime.utcnow()

    def _seconds_from_last_api_call(self) -> Optional[float]:
        if self._last_api_call:
            return (datetime.utcnow() - self._last_api_call).total_seconds()
