"""
Enhanced Maintenance activity handler.
"""

from typing import cast, override

from phederation.handlers.create import CreateHandler
from phederation.models import dereference
from phederation.models.activities import APActivity
from phederation.models.maintenance import (
    APMaintenanceCommand,
    APMaintenanceMode,
    APMaintenanceModeCancel,
    MaintenanceCommandType,
)
from phederation.models.objects import APObject
from phederation.utils.base import AccessType, ActivityType, serialize_datetime
from phederation.utils.exceptions import HandlerError, ValidationError

from .base import ActivityHandler


class MaintenanceHandler(ActivityHandler):
    """Maintenance activity handler."""

    SUPPORTED_TYPES: dict[str, type[APMaintenanceMode] | type[APMaintenanceModeCancel]] = {
        MaintenanceCommandType.MAINTENANCE_MODE.value: APMaintenanceMode,
        MaintenanceCommandType.MAINTENANCE_MODE_CANCEL.value: APMaintenanceModeCancel,
    }

    @override
    async def validate(self, activity: APActivity) -> APActivity:
        """
        Maintenance command validation.

        Validates:
        - Activity structure
        - Object type support
        - Media attachments
        - Rate limits
        """
        # Validate activity.object
        if activity.type != ActivityType.MAINTENANCE.value:
            raise ValidationError(f"APActivity is not APMaintenance (is type {activity.type})")

        obj_dict = activity.object
        if isinstance(obj_dict, APObject):
            obj_dict = obj_dict.serialize()
        if not isinstance(obj_dict, dict):
            self.logger.warning(f"Object in activity was not an APObject (instead: {type(obj_dict)}): activity {activity.serialize()}")
            raise ValidationError(f"Object in MAINTENANCE did not have proper information")
        obj_type = cast(str, obj_dict.get("type", ""))

        if not obj_type or obj_type not in self.SUPPORTED_TYPES:
            raise ValidationError(f"Unsupported object type: {obj_type}")

        # Validate object model
        model_class = self.SUPPORTED_TYPES[obj_type]
        obj = model_class.model_validate(obj_dict)

        # Check actor permissions
        actor_id = dereference(activity.actor, key="id")
        if not actor_id:
            raise ValidationError("Create: APMaintenance.actor.id is None")

        actor = await self.resolver.resolve_actor(actor_id=actor_id)
        if not actor:
            raise ValidationError("Actor not found")

        # Attribute the object to the actor of Create
        if obj.attributed_to and obj.attributed_to != actor_id:
            raise ValidationError(f"Object in Maintenance is attributed to the wrong actor: attributed_to={obj.attributed_to}, actor_id={actor.id}")
        obj.attributed_to = actor_id

        # Validate hash
        await CreateHandler.validate_content_hash(self, obj)

        # Validate content
        await self._validate_content(obj)

        # Visibility of maintenance activity and objects is always private
        maximim_security_visibility = AccessType.PRIVATE.value

        obj.visibility = maximim_security_visibility

        # Important for "store_in_collection" of server to create the object at this point, and not too late in the handle_outbox method
        activity_object = await CreateHandler.store_object_locally(handler=self, obj=obj, actor_id=actor_id)
        activity.object = activity_object

        # Set the visibility of the activity to be at most the one from the object
        activity.visibility = maximim_security_visibility

        return activity

    @override
    async def process_outbox(self, activity: APActivity) -> APActivity:
        """
        Maintenance processing.

        Handles:
        - Object storage
        - Side effects
        - Notifications
        - Federation
        """
        if not self.storage.settings.federation.admin_mode:
            raise HandlerError(
                f"Received Maintenance activity {activity.id} in outbox, but the instance is not in admin mode. Discarding the activity"
            )

        self.logger.info(f"Received Maintenance activity {activity.id} in outbox. Processing...")

        # Remove all recipients from the activity. A maintenance activity should never be sent to somebody, and certainly not federate.
        activity.clear_recipients()

        # actor_id = dereference_or_raise(activity, key="actor")
        obj = await self.resolver.resolve_object(activity.object)

        if not isinstance(obj, APMaintenanceCommand):
            raise HandlerError(f"Cannot handle command of type {type(obj).__name__} in maintenance handler")

        # Execute the maintenance command
        await self.execute_command(obj)

        return activity

    @override
    async def process_inbox(self, activity: APActivity) -> None | str:
        self.logger.warning(
            f"Received MAINTENANCE in inbox (activity id: {activity.id}). This should not happen, discarding it",
            extra={"activity_id": activity.id},
        )

        return None

    async def _validate_content(self, object: APMaintenanceCommand) -> None:
        # TODO: implement content validation
        object = object

    async def execute_command(self, object: APMaintenanceCommand):
        if not self.storage.settings.federation.admin_mode:
            raise HandlerError(f"Trying to executing command {type(object).__name__}, but not in admin mode")
        if not self.resolver.instance:
            raise HandlerError(f"Instance object in resolver not set up, cannot access maintenance commands collection")
        maintenance_commands_id = self.resolver.instance.maintenance_commands

        self.logger.critical(f"Executing command {type(object).__name__}")

        if isinstance(object, APMaintenanceMode) and object.id:
            await self.collections.add_to_collection(collection_id=maintenance_commands_id, items=object.id, access=AccessType.PRIVATE)
            self.logger.critical(
                f"Initialize maintenance mode. Start time {serialize_datetime(object.start_time)}, end time {serialize_datetime(object.end_time)}, command id {object.id}",
                extra={"x-maintenance-id": object.id},
            )
        if isinstance(object, APMaintenanceModeCancel) and object.id:
            await self.collections.remove_from_collection(
                collection_id=maintenance_commands_id, items=object.command_to_cancel, access=AccessType.PRIVATE
            )
            self.logger.critical(f"Cancelling maintenance mode command with id {object.command_to_cancel}")
