# -*- coding: utf-8 -*-
from __future__ import annotations

import datetime
import http
import uuid

from hotpocket_backend.apps.saves.services import (
    AssociationsService as BackendAssociationsService,
)
from hotpocket_soa.dto.associations import (
    AssociationOut,
    AssociationsQuery,
    AssociationUpdateIn,
    AssociationWithTargetOut,
)
from hotpocket_soa.dto.saves import SaveOut
from hotpocket_soa.exceptions.backend import NotFound

from .base import ProxyService, SOAError


class AssociationsService(ProxyService):
    class AssociationsServiceError(SOAError):
        pass

    class NotFound(AssociationsServiceError):
        pass

    class AccessDenied(AssociationsServiceError):
        pass

    def __init__(self):
        super().__init__()
        self.backend_associations_service = BackendAssociationsService()

    def get_error_class(self) -> type[SOAError]:
        return self.AssociationsServiceError

    def create(self,
               *,
               account_uuid: uuid.UUID,
               target: SaveOut,
               pk: uuid.UUID | None = None,
               created_at: datetime.datetime | None = None,
               ) -> AssociationOut:
        return AssociationOut.model_validate(
            self.call(
                self.backend_associations_service,
                'create',
                account_uuid=account_uuid,
                save_uuid=target.pk,
                pk=pk,
                created_at=created_at,
            ),
            from_attributes=True,
        )

    def get(self,
            *,
            account_uuid: uuid.UUID | None,
            pk: uuid.UUID,
            with_target: bool = False,
            allow_archived: bool = False,
            ) -> AssociationOut | AssociationWithTargetOut:
        try:
            model: type[AssociationOut] = AssociationOut
            if with_target is True:
                model = AssociationWithTargetOut

            result = model.model_validate(
                self.call(
                    self.backend_associations_service,
                    'get',
                    pk=pk,
                    with_target=with_target,
                ),
                from_attributes=True,
            )

            if allow_archived is False and result.archived_at is not None:
                raise self.NotFound(
                    http.HTTPStatus.NOT_FOUND.value, f'pk=`{pk}`',
                )

            if account_uuid is not None and result.account_uuid != account_uuid:
                raise self.AccessDenied(
                    http.HTTPStatus.FORBIDDEN.value,
                    f'account_uuid=`{account_uuid}` pk=`{pk}`',
                )

            return result
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)

    def search(self,
               *,
               query: AssociationsQuery,
               limit: int,
               ) -> list[AssociationWithTargetOut]:
        return [
            AssociationWithTargetOut.model_validate(row, from_attributes=True)
            for row
            in self.call(
                self.backend_associations_service,
                'search',
                query=query,
                limit=limit,
            )
        ]

    def archive(self, *, association: AssociationOut) -> bool:
        try:
            return self.call(
                self.backend_associations_service,
                'archive',
                pk=association.pk,
            )
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)

    def star(self, *, association: AssociationOut) -> AssociationOut:
        try:
            return AssociationOut.model_validate(
                self.call(
                    self.backend_associations_service,
                    'star',
                    pk=association.pk,
                ),
                from_attributes=True,
            )
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)

    def unstar(self, *, association: AssociationOut) -> AssociationOut:
        try:
            return AssociationOut.model_validate(
                self.call(
                    self.backend_associations_service,
                    'unstar',
                    pk=association.pk,
                ),
                from_attributes=True,
            )
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)

    def update(self,
               *,
               association: AssociationOut,
               update: AssociationUpdateIn,
               ) -> AssociationOut:
        try:
            return AssociationOut.model_validate(
                self.call(
                    self.backend_associations_service,
                    'update',
                    pk=association.pk,
                    update=update,
                ),
                from_attributes=True,
            )
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)

    def delete(self, *, association: AssociationOut) -> bool:
        try:
            return self.call(
                self.backend_associations_service,
                'delete',
                pk=association.pk,
            )
        except NotFound as exception:
            raise self.NotFound.from_backend_error(exception)
