"""
JSON serializer for ActivityPub objects.
"""

import json
from typing import Any, ClassVar

from pydantic import BaseModel, SerializerFunctionWrapHandler, model_serializer
from pydantic.alias_generators import to_camel
from pydantic.config import ConfigDict


def to_json_string(data: dict[str, Any]) -> str:
    """
    Convert dictionary to JSON string with standardized formatting.

    Args:
        data: Dictionary to convert

    Returns:
        JSON string with consistent formatting
    """
    return json.dumps(data, sort_keys=True, ensure_ascii=True, separators=(",", ":"))


class ActivityPubBase(BaseModel):
    """Base class for all ActivityPub objects."""

    @staticmethod
    def include_context(data: dict[str, Any]):
        context = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"]
        if "proof" in data and isinstance(data["proof"], dict):
            context.append("https://w3id.org/security/data-integrity/v2")
        data["@context"] = context
        return data

    @model_serializer(mode='wrap')
    def auto_serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
        serialized: dict[str, Any] = handler(self)  # pyright: ignore[reportAny]
        # Alwas include JSON-LD context
        return ActivityPubBase.include_context(serialized)

    def serialize(self, include_context: bool = True) -> dict[str, Any]:
        """Serialize the AP object to a dict.

        Args:
            include_context (bool, optional): If the JSON-LD context for ActivityPub should be included in the dict. Defaults to True.

        Returns:
            dict[str, Any]: JSON-LD representation of the object (if include_context=True), otherwise regular JSON.
        """
        processed_data = self.model_dump(by_alias=True, exclude_none=True)

        # Remove context if not needed
        if not include_context:
            processed_data.pop("@context", None)

        return processed_data

    @classmethod
    def deserialize(cls, data: str | dict[str, Any]) -> "ActivityPubBase":
        """Deserialize dict to an ActivityPub object.
        This is removing the json-ld context before deserialization.
        """
        if isinstance(data, dict):
            data.pop("@context", None)
        obj = cls.model_validate(data)
        return obj

    model_config: ClassVar[ConfigDict] = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
        extra="allow",
        arbitrary_types_allowed=True,
        loc_by_alias=True,
    )
