"""ActivityPub models.

Includes the models defined in ActivityPub, such as APObject, APActor, APDocument and all activities, such as APCreate, APBlock, and so on.
"""

from abc import ABCMeta, abstractmethod
from typing import Any, ClassVar, Generic, TypeVar

from pydantic import BaseModel, ConfigDict

from phederation.utils import ActivityType, NodeInfo, ObjectType
from phederation.utils.base import AccessType, ActivityPubBaseWithId, ObjectId
from phederation.utils.exceptions import StorageError, ValidationError, catch_exceptions

from .activities import (
    APAccept,
    APActivity,
    APAnnounce,
    APBlock,
    APCreate,
    APDelete,
    APFollow,
    APLike,
    APMigrate,
    APReject,
    APRemove,
    APAdd,
    APUndo,
    APUpdate,
    ActivityInQueue,
    ErrorMessageActivityHandler,
    ErrorMessageActivityNotFound,
    ErrorMessageActivityRestricted,
    ErrorMessageActivityValidation,
)
from .actors import (
    APAccount,
    APActor,
    APApplication,
    APGroup,
    APOrganization,
    APPerson,
    APService,
    APUser,
    ActorType,
    ErrorMessageActorUnauthorized,
)
from .collections import (
    APCollection,
    APCollectionPage,
    APOrderedCollection,
    APOrderedCollectionPage,
    APUsersCollection,
    ValidCollection,
)
from .keys import APPrivateKey, APPublicKey, ErrorMessageKeyNotFound
from .links import APLink, APMention, RelativeLink, RelativeLinkTemplate
from .maintenance import (
    APMaintenance,
    APMaintenanceCommand,
    APMaintenanceMode,
    APMaintenanceModeCancel,
    MaintenanceCommandType,
)
from .media import MediaMetadata
from .objects import (
    APArticle,
    APAudio,
    APCollectionItem,
    APDocument,
    APEvent,
    APImage,
    APNote,
    APObject,
    APPage,
    APPlace,
    APProfile,
    APRelationship,
    APTombstone,
    APUserData,
    APVideo,
    DocumentType,
    dereference,
    dereference_or_raise,
)
from .proofs import DataIntegrityProof

T = TypeVar("T", bound=ActivityPubBaseWithId)


class CRUD(BaseModel, Generic[T], metaclass=ABCMeta):
    """Base class for CRUD operations."""

    model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)

    @abstractmethod
    async def get_table_name(self) -> str:
        pass

    @catch_exceptions(StorageError, "Failed to create_many")
    @abstractmethod
    async def create_many(self, data: list[T]) -> list[ObjectId]:
        pass

    @catch_exceptions(StorageError, "Failed to create")
    @abstractmethod
    async def create(self, data: T, raise_if_exists: bool = True) -> ObjectId:
        pass

    @catch_exceptions(StorageError, "Failed to read")
    @abstractmethod
    async def read(self, id: ObjectId) -> T | None:
        pass

    @catch_exceptions(StorageError, "Failed to select")
    @abstractmethod
    async def select(
        self, collection_id: ObjectId | None = None, stored_ids: list[ObjectId] | None = None, access: AccessType = AccessType.PUBLIC
    ) -> list[T]:
        pass

    @catch_exceptions(StorageError, "Failed to count")
    @abstractmethod
    async def count(self, collection_id: ObjectId | None = None, access: AccessType = AccessType.PUBLIC) -> int:
        pass

    @catch_exceptions(StorageError, "Failed to upsert")
    @abstractmethod
    async def upsert(self, id: ObjectId, data: T) -> ObjectId:
        pass

    @catch_exceptions(StorageError, "Failed to delete")
    @abstractmethod
    async def delete(self, id: ObjectId | list[ObjectId]) -> bool:
        pass


type_to_obj_dict = {
    ActivityType.ACCEPT.value: APAccept,
    ActivityType.ANNOUNCE.value: APAnnounce,
    ActivityType.BLOCK.value: APBlock,
    ActivityType.CREATE.value: APCreate,
    ActivityType.DELETE.value: APDelete,
    ActivityType.FOLLOW.value: APFollow,
    ActivityType.LIKE.value: APLike,
    ActivityType.REJECT.value: APReject,
    ActivityType.REMOVE.value: APRemove,
    ActivityType.ADD.value: APAdd,
    ActivityType.UNDO.value: APUndo,
    ActivityType.UPDATE.value: APUpdate,
    ActivityType.MIGRATE.value: APMigrate,
    ActivityType.ACTIVITY_IN_QUEUE.value: ActivityInQueue,
    ActorType.APPLICATION.value: APApplication,
    ActorType.GROUP.value: APGroup,
    ActorType.ORGANIZATION.value: APOrganization,
    ActorType.PERSON.value: APPerson,
    ActorType.SERVICE.value: APService,
    ActorType.ACTOR.value: APActor,
    ActorType.ACCOUNT.value: APAccount,
    ActorType.PROFILE.value: APProfile,
    ActorType.USER.value: APUser,
    ActorType.PERSON.value: APPerson,
    ActorType.APPLICATION.value: APApplication,
    ActorType.ORGANIZATION.value: APOrganization,
    ActorType.GROUP.value: APGroup,
    ActorType.SERVICE.value: APService,
    DocumentType.ARTICLE.value: APArticle,
    DocumentType.AUDIO.value: APAudio,
    DocumentType.VIDEO.value: APVideo,
    DocumentType.DOCUMENT.value: APDocument,
    DocumentType.IMAGE.value: APImage,
    DocumentType.NOTE.value: APNote,
    DocumentType.USERDATA.value: APUserData,
    ObjectType.COLLECTION.value: APCollection,
    ObjectType.COLLECTION_PAGE.value: APCollectionPage,
    ObjectType.ORDERED_COLLECTION.value: APOrderedCollection,
    ObjectType.ORDERED_COLLECTION_PAGE.value: APOrderedCollectionPage,
    ObjectType.COLLECTION_ITEM.value: APCollectionItem,
    ObjectType.OBJECT.value: APObject,
    ObjectType.PAGE.value: APPage,
    ObjectType.LINK.value: APLink,
    ObjectType.MENTION.value: APMention,
    ObjectType.EVENT.value: APEvent,
    ObjectType.PLACE.value: APPlace,
    ObjectType.RELATIONSHIP.value: APRelationship,
    ObjectType.KEY.value: APPrivateKey,
    ObjectType.DATA_INTEGRITY_PROOF.value: DataIntegrityProof,
    ObjectType.TOMBSTONE.value: APTombstone,
    ObjectType.NODE_INFO.value: NodeInfo,
    ActivityType.MAINTENANCE.value: APMaintenance,
    MaintenanceCommandType.MAINTENANCE_MODE.value: APMaintenanceMode,
    MaintenanceCommandType.MAINTENANCE_MODE_CANCEL.value: APMaintenanceModeCancel,
}


def object_from_type(obj: APObject | dict[str, Any]):
    object_type = dereference_or_raise(obj, "type")
    if isinstance(obj, APObject):
        obj = obj.serialize()
    model_class = type_to_obj_dict.get(object_type, None)
    if model_class:
        obj_new = model_class.deserialize(obj)
    else:
        raise ValidationError(f"Not able to deserialize APObject of type {object_type}")
    return obj_new


# Export all classes
__all__ = [
    "CRUD",
    "T",
    "MediaMetadata",
    "dereference",
    "APPublicKey",
    "APPrivateKey",
    "ActivityPubBaseWithId",
    "APObject",
    "APLink",
    "APActivity",
    "RelativeLink",
    "RelativeLinkTemplate",
    "APActor",
    "ActorType",
    "APUser",
    "APPerson",
    "APGroup",
    "APOrganization",
    "APApplication",
    "APService",
    "APAccount",
    "APEvent",
    "APPlace",
    "APProfile",
    "APRelationship",
    "APTombstone",
    "APArticle",
    "APAudio",
    "APDocument",
    "APImage",
    "APNote",
    "APPage",
    "APVideo",
    "APCollection",
    "APOrderedCollection",
    "APCollectionPage",
    "APOrderedCollectionPage",
    "ValidCollection",
    "APUsersCollection",
    "APCreate",
    "APUpdate",
    "APDelete",
    "APFollow",
    "APUndo",
    "APLike",
    "APAnnounce",
    "APMention",
    "APAccept",
    "APAdd",
    "APRemove",
    "APBlock",
    "APReject",
    "APMigrate",
    "APMaintenanceCommand",
    "APMaintenanceModeCancel",
    "APUserData",
    "APMaintenance",
    "MaintenanceCommandType",
    "ErrorMessageActivityNotFound",
    "ErrorMessageActivityRestricted",
    "ErrorMessageKeyNotFound",
    "ErrorMessageActivityValidation",
    "ErrorMessageActivityHandler",
    "ErrorMessageActorUnauthorized",
    "DataIntegrityProof",
]
