"""
Delete activity handler implementation.
"""

from datetime import datetime, timezone
from typing import override

from phederation.models import APTombstone, ValidCollection, dereference
from phederation.models.activities import APActivity
from phederation.models.objects import dereference_or_raise
from phederation.utils import ObjectId, ObjectType
from phederation.utils.base import AccessType
from phederation.utils.exceptions import AuthorizationError, ValidationError

from .base import ActivityHandler


class DeleteHandler(ActivityHandler):
    """Handle Delete activities."""

    @override
    async def validate(self, activity: APActivity) -> APActivity:
        """Validate Delete activity."""
        # Verify actor permissions
        actor_id = dereference_or_raise(activity, key="actor")
        actor_obj = await self.actor_manager.actor_from_storage(id=actor_id)
        if not actor_obj:
            raise ValidationError("Actor not found")

        # Verify object ownership
        object_id = dereference(activity, key="object")
        if not object_id:
            self.logger.warning("DeleteHandler: Object id not found.")
            raise ValidationError("Cannot find object id")

        if not (await DeleteHandler.check_is_collection(self, actor_id=actor_id, object_id=object_id)):
            stored_object = await self.storage.object.read(id=object_id)
            if not stored_object:
                self.logger.warning("DeleteHandler: Object not found on this instance. Failing silently.")
                return activity

            if stored_object.attributed_to != actor_id:
                raise AuthorizationError("Not authorized to delete this object")

        return activity

    @staticmethod
    async def check_is_collection(handler: ActivityHandler, actor_id: ObjectId, object_id: ObjectId):
        actor_obj = await handler.actor_manager.actor_from_storage(id=actor_id)
        assert actor_obj
        actor_collections_id = dereference(actor_obj, ValidCollection.Collections.value)
        if actor_collections_id and object_id.startswith(actor_collections_id):
            return True
        return False

    async def delete_collection(self, actor_id: ObjectId, actor_collection_id: ObjectId):
        """Remove the entire actor-created collection.

        This removes all items from the collection and also removes the collection link from the actor collections.

        Args:
            actor_id (ObjectId): Id of the actor.
            actor_collection_id (ObjectId): The id of the collection to remove.
        """
        self.logger.info(f"Deleting the actor collection {actor_collection_id}")
        await self.collections.delete_collection(actor_collection_id)

        actor_obj = await self.actor_manager.actor_from_storage(id=actor_id)
        if actor_obj and actor_obj.collections:
            await self.collections.remove_from_collection(actor_obj.collections, items=actor_collection_id, access=AccessType.PRIVATE)

    async def handle_delete(self, activity: APActivity) -> APActivity:
        object_id = dereference_or_raise(activity, key="object")
        actor_id = dereference_or_raise(activity, "actor")

        # check if the object id references an actor collection -> then delete that one
        if await DeleteHandler.check_is_collection(self, actor_id=actor_id, object_id=object_id):
            await self.delete_collection(actor_id=actor_id, actor_collection_id=object_id)
            return activity

        # get object data and then delete it from storage
        obj = await self.storage.object.read(id=object_id)
        if not obj:
            self.logger.warning("DeleteHandler: Object not found on this instance. Failing silently.")
            return activity

        _ = await self.storage.object.delete(id=object_id)

        attributed_to_id = dereference(obj.attributed_to, "id")

        if not attributed_to_id:
            self.logger.warning("DeleteHandler: Object property 'attributed_to' is not set. Failing silently.")
            return activity

        # Create the tombstone at the object_id in the storage
        await self._create_tombstone(object_id=object_id, former_type=ObjectType(obj.type), attributed_to=attributed_to_id)

        # Update collections
        await self._update_collections(object_id=object_id, actor_id=attributed_to_id)

        return activity

    @override
    async def process_outbox(self, activity: APActivity) -> APActivity:
        """Process Delete activity."""
        return await self.handle_delete(activity)

    async def _create_tombstone(self, object_id: ObjectId, former_type: ObjectType, attributed_to: ObjectId) -> None:
        """Create tombstone for deleted object."""
        tombstone = APTombstone(former_type=str(former_type), deleted_at=datetime.now(timezone.utc), id=object_id, attributed_to=attributed_to)
        _ = await self.storage.object.create(tombstone)

    async def _update_collections(self, object_id: ObjectId, actor_id: ObjectId) -> None:
        """Update collections after deletion."""
        # TODO: clean up links
        # remove from the owning actors objects collection, if it exists
        actor = await self.resolver.resolve_actor(actor_id=actor_id)
        if actor.objects:
            # the access type is PRIVATE here, because the original actor tries to delete something they created.
            await self.collections.remove_from_collection(collection_id=actor.objects, items=object_id, access=AccessType.PRIVATE)

    @override
    async def process_inbox(self, activity: APActivity) -> None | str:
        activity = await self.handle_delete(activity)
        return activity.id
