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

import datetime
import logging
import uuid

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.timezone import now

from hotpocket_backend.apps.core.services import get_adapter
from hotpocket_backend.apps.saves.models import Association, Save
from hotpocket_backend.apps.saves.types import PAssociationAdapter
from hotpocket_soa.dto.associations import (
    AssociationsQuery,
    AssociationUpdateIn,
)
from hotpocket_soa.exceptions.backend import (
    Invalid as InvalidError,
    NotFound as NotFoundError,
)

from .saves import SavesService

LOGGER = logging.getLogger(__name__)


class AssociationsService:
    class AssociationsServiceError(Exception):
        pass

    class Invalid(InvalidError, AssociationsServiceError):
        pass

    class NotFound(NotFoundError, AssociationsServiceError):
        pass

    @property
    def adapter(self) -> PAssociationAdapter:
        if hasattr(self, '_adapter') is False:
            adapter_klass = get_adapter(
                'SAVES_ASSOCIATION_ADAPTER',
                'hotpocket_backend.apps.saves.adapters.basic:BasicAssociationAdapter',
            )
            self._adapter = adapter_klass()

        return self._adapter

    def create(self,
               *,
               account_uuid: uuid.UUID,
               save_uuid: uuid.UUID,
               pk: uuid.UUID | None = None,
               created_at: datetime.datetime | None = None,
               ) -> Association:
        try:
            save = SavesService().get(pk=save_uuid)

            defaults = dict(
                account_uuid=account_uuid,
                target=save,
            )

            if pk is not None:
                defaults['id'] = pk

            result, created = Association.objects.get_or_create(
                account_uuid=account_uuid,
                deleted_at__isnull=True,
                target=save,
                archived_at__isnull=True,
                defaults=defaults,
            )

            if created is True:
                if created_at is not None:
                    result.created_at = created_at
                    result.save()

            return result
        except ValidationError as exception:
            raise self.Invalid.from_django_validation_error(exception)

    def get(self,
            *,
            pk: uuid.UUID,
            with_target: bool = False,
            ) -> Association:
        try:
            query_set = Association.active_objects.\
                filter(
                    target__deleted_at__isnull=True,
                )

            if with_target is True:
                query_set = query_set.select_related('target')

            return query_set.get(pk=pk)
        except Association.DoesNotExist as exception:
            raise self.NotFound(
                f'Association not found: pk=`{pk}`',
            ) from exception

    def search(self,
               *,
               query: AssociationsQuery,
               offset: int = 0,
               limit: int = 10,
               order_by: str = '-pk',
               ) -> models.QuerySet[Save]:
        filters = self.adapter.get_search_filters(query=query)

        result = Association.active_objects.\
            select_related('target').\
            filter(*filters).\
            order_by(order_by)

        return result[offset:offset + limit]

    def update(self,
               *,
               pk: uuid.UUID,
               update: AssociationUpdateIn,
               ) -> Association:
        try:
            association = self.get(pk=pk)
            association.target_title = update.target_title
            association.target_description = update.target_description

            next_target_meta = {
                **(association.target_meta or {}),
            }

            next_target_meta.pop('title', None)
            next_target_meta.pop('description', None)
            association.target_meta = next_target_meta

            association.save()

            return association
        except ValidationError as exception:
            raise self.Invalid.from_django_validation_error(exception)

    def archive(self, *, pk: uuid.UUID) -> bool:
        association = self.get(pk=pk)
        association.archived_at = now()
        association.save()

        return True

    def star(self, *, pk: uuid.UUID) -> Association:
        association = self.get(pk=pk)

        if association.starred_at is None:
            association.starred_at = now()
            association.save()

        return association

    def unstar(self, *, pk: uuid.UUID) -> Association:
        association = self.get(pk=pk)

        if association.starred_at is not None:
            association.starred_at = None
            association.save()

        return association

    def delete(self, *, pk: uuid.UUID) -> bool:
        association = self.get(pk=pk)
        association.soft_delete()

        return True
