"""
Handler for Reject activities.
"""

from typing import override

from phederation.models import APObject, dereference
from phederation.models.activities import APActivity
from phederation.utils import ActivityType, ObjectId
from phederation.utils.exceptions import HandlerError, ValidationError

from .base import ActivityHandler


class RejectHandler(ActivityHandler):
    """Handles Reject activities."""

    REJECTABLE_TYPES: list[str] = ["Follow"]

    @override
    async def process_outbox(self, activity: APActivity) -> APActivity:
        """Handle Reject activity."""
        actor_id = dereference(activity, key="actor")
        follow_activity_obj_or_str = activity.object

        if not follow_activity_obj_or_str:
            raise HandlerError("follow_activity_obj is None")

        # Resolve activity
        follow_activity_obj = await self.resolver.resolve_activity(follow_activity_obj_or_str)

        # Verify authorization
        if dereference(follow_activity_obj, key="object") != actor_id:
            raise HandlerError("Unauthorized: can only reject follow activities targeting self")

        follower_id = dereference(follow_activity_obj, key="actor")
        following_id = dereference(follow_activity_obj, key="object")

        if not follower_id or not following_id or not follow_activity_obj.id:
            raise HandlerError("follower_id or following_id or follow_activity_obj.id of activity is None")

        # Handle based on rejected activity type
        if follow_activity_obj.type == "Follow":
            # Check if follow request exists and hasn't been accepted yet
            is_following = await self.storage.is_following(follower=follower_id, following=following_id, check_accepted=True)

            if is_following:
                raise HandlerError("Follow request already accepted")

            await self._handle_reject_follow(
                follow_activity_id=follow_activity_obj.id, follower=follower_id, following=following_id, reason=activity.content
            )

        # Notify original actor's inbox
        if follower_id:
            _ = await self.delivery.deliver_activity(activity, recipients=[follower_id])

        self.logger.info(f"Handled Reject activity: {actor_id} -> {follow_activity_obj.id}")
        return activity

    async def _handle_reject_follow(self, follow_activity_id: ObjectId, follower: ObjectId, following: ObjectId, reason: str | None = None) -> None:
        """Handle rejecting a Follow."""
        self.logger.info(f"Rejecting follow of {follower} following {following}, reason: {reason}.")
        _ = await self.storage.follow.delete(id=follow_activity_id)

    @override
    async def validate(self, activity: APActivity) -> APActivity:
        """Validate Reject activity."""
        if activity.type != "Reject":
            raise ValidationError(f"Invalid activity type: '{activity.type}'")

        object_data = activity.object
        if not object_data:
            raise ValidationError("Reject must have an object")

        if isinstance(object_data, APObject):
            object_type = object_data.type
            if object_type not in self.REJECTABLE_TYPES:
                raise ValidationError(f"Cannot reject activity type: {object_type}")

        return activity

    @override
    async def process_inbox(self, activity: APActivity) -> None | str:
        """
        Receive a Reject in the inbox.
        This means the current actor tried to follow somebody and they rejected it, sending this back to the actor with this inbox.
        """
        actor_id = dereference(activity, key="actor")
        follow_activity_obj = activity.object

        if not follow_activity_obj:
            raise HandlerError("follow_activity_obj is None")

        # Resolve object if it's a string ID
        follow_activity_obj = await self.resolver.resolve_activity(follow_activity_obj)

        follower_id = dereference(follow_activity_obj, key="actor")
        following_id = dereference(follow_activity_obj, key="object")

        if not follower_id or not following_id or not follow_activity_obj.id:
            raise HandlerError("Could not dereference actor or object from follow activity")

        # Handle based on rejected activity type
        if follow_activity_obj.type == ActivityType.FOLLOW.value:
            # Check if follow request exists and hasn't been accepted yet
            is_following = await self.storage.is_following(follower=follower_id, following=following_id, check_accepted=True)

            if is_following:
                raise HandlerError("Follow request on local instance already stored as accepted")

            # remove the locally stored follow
            await self._handle_reject_follow(
                follow_activity_id=follow_activity_obj.id, follower=follower_id, following=following_id, reason=activity.content
            )
        else:
            raise HandlerError(f"Received a Reject activity that was not for a Follow activity (type: {follow_activity_obj.type})")

        self.logger.info(f"Handled Reject activity in inbox: {actor_id} -> {follow_activity_obj.id}")
