"""
activities.py
This module defines the Activity types for ActivityPub.
These activities are based on the ActivityStreams vocabulary.

For more information, see:
https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
"""

from typing import ClassVar, override

from pydantic import ConfigDict, Field, SerializeAsAny

from phederation.utils import ActivityPubBaseWithId, ActivityType, ObjectId
from phederation.utils.base import APDateTime

from .errors import BaseError
from .objects import APDocument, APObject


class ErrorMessageActivityNotFound(BaseError):
    message: str = "Activity does not exist locally."
    description: str = "The activity with the given id cannot be found on this instance."


class ErrorMessageActivityRestricted(BaseError):
    message: str = "Activity has restricted visibility."
    description: str = "The activity with the given id has restricted visibility."


class ErrorMessageActivityValidation(BaseError):
    message: str = "Activity request body is not valid."
    description: str = "The activity posted has an invalid request body."


class ErrorMessageActivityHandler(BaseError):
    message: str = "Activity could not be processed properly."
    description: str = "The inbox could not process the given activity request body."


class APActivity(APObject):
    """
    Base class for all ActivityPub activities.
    An Activity is a subtype of Object that describes some form of action.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#activity
    Usage: https://www.w3.org/TR/activitypub/#activities
    """

    actor: str | SerializeAsAny[APObject] = Field(..., description="The actor performing the activity")
    object: None | str | SerializeAsAny[APObject] = Field(default=None, description="The object of the activity")
    target: None | str | SerializeAsAny[APObject] = Field(default=None, description="The target of the activity")
    result: None | str | SerializeAsAny[APObject] = Field(default=None, description="The result of the activity")
    origin: None | str | SerializeAsAny[APObject] = Field(default=None, description="The origin of the activity")
    instrument: None | str | SerializeAsAny[APObject] = Field(default=None, description="The instrument used")

    @override
    def clear_recipients(self):
        super().clear_recipients()
        if self.object and not isinstance(self.object, str):
            self.object.clear_recipients()

    @override
    async def get_recipients(self) -> list[ObjectId]:
        """Get the (deduplicated) list of actors in the activity recipient fields (to,cc,bto,bcc,audience).
        This includes the activity.object recipients. Does *not* resolve the inboxes of these recipients yet.

        Returns:
            list[ObjectId]: List of actor_id's for all recipients found.
        """
        recipients = await super().get_recipients()
        object = self.object
        if isinstance(object, dict):
            object = APObject.deserialize(object)
        if isinstance(object, APObject):
            recipients += await object.get_recipients()

        recipients = list(set(recipients))
        return recipients

    def get_object_or_None(self):
        """Returns the activity.object as APObject or APDocument if it is set (also if it is a dict).

        If the activity.object is None or just an ObjectId string, this returns None.

        Returns:
            APObject | None: the object or None.
        """
        if isinstance(self.object, APObject):
            return self.object

    model_config: ClassVar[ConfigDict] = ConfigDict(
        json_schema_extra={
            "examples": [
                {
                    "id": "https://example.org/id-of-the-activity-unused",
                    "type": "Create",
                    "attributedTo": "https://example.org/id-of-an-actor",
                    "content": "The content of the activity described in text.",
                    "summary": "A summary of the activity.",
                    "actor": "https://example.org/id-of-actor-performing-the-activity",
                    "object": {"id": "https://example.org/id-of-the-note-temporary", "type": "Note", "summary": "This is a note to create."},
                    "target": "https://example.org/id-of-target-actor-or-object",
                }
            ]
        }
    )


class APCreate(APActivity):
    """
    Represents a Create activity in ActivityPub.
    Indicates that the actor has created the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#create
    """

    type: str = Field(
        default=ActivityType.CREATE.value,
        description="Indicates that this object represents a create activity",
    )
    object: None | str | APDocument | APObject = Field(default=None, description="The object being created")


class APUpdate(APActivity):
    """
    Represents an Update activity in ActivityPub.
    Indicates that the actor has updated the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#update
    """

    type: str = Field(
        default=ActivityType.UPDATE.value,
        description="Indicates that this object represents an update activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being updated")


class APDelete(APActivity):
    """
    Represents a Delete activity in ActivityPub.
    Indicates that the actor has deleted the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#delete
    """

    type: str = Field(
        default=ActivityType.DELETE.value,
        description="Indicates that this object represents a delete activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being deleted")


class APFollow(APActivity):
    """
    Represents a Follow activity in ActivityPub.
    Indicates that the actor is "following" the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#follow
    """

    type: str = Field(
        default=ActivityType.FOLLOW.value,
        description="Indicates that this object represents a follow activity",
    )
    object: None | str | APObject = Field(default=..., description="The object being followed")
    accepted: bool = Field(default=False, description="If the object has accepted the follow of actor")
    accepted_at: APDateTime | None = Field(default=None, description="Time of accept of follow.")


class APUndo(APActivity):
    """
    Represents an Undo activity in ActivityPub.
    Indicates that the actor is undoing the object activity.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#undo
    """

    type: str = Field(
        default=ActivityType.UNDO.value,
        description="Indicates that this object represents an undo activity",
    )
    object: None | str | APObject = Field(default=None, description="The activity being undone")


class APLike(APActivity):
    """
    Represents a Like activity in ActivityPub.
    Indicates that the actor likes the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#like
    """

    type: str = Field(
        default=ActivityType.LIKE.value,
        description="Indicates that this object represents a like activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being liked")


class APAnnounce(APActivity):
    """
    Represents an Announce activity in ActivityPub.
    Indicates that the actor is calling the target's attention to the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#announce
    """

    type: str = Field(
        default=ActivityType.ANNOUNCE.value,
        description="Indicates that this object represents an announce activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being announced")


class APAccept(APActivity):
    """
    Represents an Accept activity in ActivityPub.
    Indicates that the actor accepts the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#accept
    """

    type: str = Field(
        default=ActivityType.ACCEPT.value,
        description="Indicates that this object represents an accept activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being accepted")


class APAdd(APActivity):
    """
    Represents a Add activity in ActivityPub.
    Indicates that the actor is adding the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#add
    """

    type: str = Field(
        default=ActivityType.ADD.value,
        description="Activity to add an object to an origin",
    )
    object: None | str | APObject = Field(default=None, description="The object being added")
    origin: None | str | APObject = Field(default=None, description="The origin where the object should be added to")


class APRemove(APActivity):
    """
    Represents a Remove activity in ActivityPub.
    Indicates that the actor is removing the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#remove
    """

    type: str = Field(
        default=ActivityType.REMOVE.value,
        description="Activity to remove an object from an origin",
    )
    object: None | str | APObject = Field(default=None, description="The object being removed")
    origin: None | str | APObject = Field(default=None, description="The origin where the object resides now, and where it should be removed from")


class APBlock(APActivity):
    """
    Represents a Block activity in ActivityPub.
    Indicates that the actor is blocking the object.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#block
    """

    type: str = Field(
        default=ActivityType.BLOCK.value,
        description="Indicates that this object represents a block activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being blocked")


class APReject(APActivity):
    """
    Represents a Reject activity in ActivityPub.
    Indicates that the actor rejects the object.
    This is typically used to reject incoming activities such as Follow requests.

    Specification: https://www.w3.org/TR/activitystreams-vocabulary/#reject
    """

    type: str = Field(
        default=ActivityType.REJECT.value,
        description="A reject activity",
    )
    object: None | str | APObject = Field(default=None, description="The object being rejected")


class APMigrate(APActivity):
    """Represents a Migrate activity.

    This is not part of the official ActivityPub specification.
    It allows to migrate user accounts to new instances, or between accounts on the same instance.
    """

    type: str = Field(default=ActivityType.MIGRATE.value, description="A migrate activity")
    actor_to: ObjectId | None = Field(
        default=None,
        description="Actor to migrate to. Can be on a new instance, or on the same instance. If set to None, this can be used to download the migration data locally and use it for migration somewhere else",
    )
    accepted_at: APDateTime | None = Field(
        default=None,
        description="If the actor on the new instance has accepted the APMigrate activity. This is the last step before migration is completed",
    )


class ActivityInQueue(ActivityPubBaseWithId):
    type: str = ActivityType.ACTIVITY_IN_QUEUE.value
    activity: APActivity = Field(..., description="The activity to send")
    recipients: list[ObjectId] = Field(
        ...,
        description="The list of ids of the recipient actors. This can include the bcc/bto fields that should be removed already from the given activity",
    )
    send_at: APDateTime = Field(..., description="When to attempt to send the given activity")
