"""Provide models for new modmail."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from praw.const import API_PATH
from praw.models.reddit.base import RedditBase
from praw.util import snake_case_keys

if TYPE_CHECKING:
    import praw


class ModmailObject(RedditBase):
    """A base class for objects within a modmail conversation."""

    AUTHOR_ATTRIBUTE = "author"
    STR_FIELD = "id"

    def __setattr__(self, attribute: str, value: Any) -> None:
        """Objectify the AUTHOR_ATTRIBUTE attribute."""
        if attribute == self.AUTHOR_ATTRIBUTE:
            value = self._reddit._objector.objectify(data=value)
        super().__setattr__(attribute, value)


class ModmailConversation(RedditBase):
    """A class for modmail conversations.

    .. include:: ../../typical_attributes.rst

    ==================== ===============================================================
    Attribute            Description
    ==================== ===============================================================
    ``authors``          Provides an ordered list of :class:`.Redditor` instances. The
                         authors of each message in the modmail conversation.
    ``id``               The ID of the :class:`.ModmailConversation`.
    ``is_highlighted``   Whether or not the :class:`.ModmailConversation` is
                         highlighted.
    ``is_internal``      Whether or not the :class:`.ModmailConversation` is a private
                         mod conversation.
    ``last_mod_update``  Time of the last mod message reply, represented in the `ISO
                         8601`_ standard with timezone.
    ``last_updated``     Time of the last message reply, represented in the `ISO 8601`_
                         standard with timezone.
    ``last_user_update`` Time of the last user message reply, represented in the `ISO
                         8601`_ standard with timezone.
    ``num_messages``     The number of messages in the :class:`.ModmailConversation`.
    ``obj_ids``          Provides a list of dictionaries representing mod actions on the
                         :class:`.ModmailConversation`. Each dict contains attributes of
                         ``"key"`` and ``"id"``. The key can be either ``""messages"``
                         or ``"ModAction"``. ``"ModAction"`` represents
                         archiving/highlighting etc.
    ``owner``            Provides an instance of :class:`.Subreddit`. The subreddit that
                         the :class:`.ModmailConversation` belongs to.
    ``participant``      Provides an instance of :class:`.Redditor`. The participating
                         user in the :class:`.ModmailConversation`.
    ``subject``          The subject of the :class:`.ModmailConversation`.
    ==================== ===============================================================

    .. _iso 8601: https://en.wikipedia.org/wiki/ISO_8601

    """

    DEFAULT_NUMBER_OF_MUTE_DAYS = 3
    STR_FIELD = "id"

    @staticmethod
    def _convert_conversation_objects(data: dict[str, Any], reddit: praw.Reddit) -> None:
        """Convert messages and mod actions to PRAW objects."""
        result = {"messages": [], "modActions": []}
        for thing in data["objIds"]:
            key = thing["key"]
            thing_data = data[key][thing["id"]]
            result[key].append(reddit._objector.objectify(data=thing_data))
        data.update(result)

    @staticmethod
    def _convert_user_summary(data: dict[str, Any], reddit: praw.Reddit) -> None:
        """Convert dictionaries of recent user history to PRAW objects."""
        parsers = {
            "recentComments": reddit._objector.parsers[reddit.config.kinds["comment"]],
            "recentConvos": ModmailConversation,
            "recentPosts": reddit._objector.parsers[reddit.config.kinds["submission"]],
        }
        for kind, parser in parsers.items():
            objects = []
            for thing_id, summary in data[kind].items():
                thing = parser(reddit, id=thing_id.rsplit("_", 1)[-1])
                if parser is not ModmailConversation:
                    del summary["permalink"]
                for key, value in summary.items():
                    setattr(thing, key, value)
                objects.append(thing)
            # Sort by id, oldest to newest
            data[kind] = sorted(objects, key=lambda x: int(x.id, base=36), reverse=True)

    @classmethod
    def parse(
        cls,
        data: dict[str, Any],
        reddit: praw.Reddit,
    ) -> ModmailConversation:
        """Return an instance of :class:`.ModmailConversation` from ``data``.

        :param data: The structured data.
        :param reddit: An instance of :class:`.Reddit`.

        """
        data["authors"] = [reddit._objector.objectify(data=author) for author in data["authors"]]
        for entity in "owner", "participant":
            data[entity] = reddit._objector.objectify(data=data[entity])

        if data.get("user"):
            cls._convert_user_summary(data["user"], reddit)
            data["user"] = reddit._objector.objectify(data=data["user"])

        data = snake_case_keys(data)

        return cls(reddit, _data=data)

    def __init__(
        self,
        reddit: praw.Reddit,
        id: str | None = None,
        *,
        mark_read: bool = False,
        _data: dict[str, Any] | None = None,
    ) -> None:
        """Initialize a :class:`.ModmailConversation` instance.

        :param mark_read: If ``True``, conversation is marked as read (default:
            ``False``).

        """
        if bool(id) == bool(_data):
            msg = "Either 'id' or '_data' must be provided."
            raise TypeError(msg)

        if id:
            self.id = id

        super().__init__(reddit, _data=_data)

        self._info_params = {"markRead": True} if mark_read else None

    def _build_conversation_list(self, other_conversations: list[ModmailConversation]) -> str:
        """Return a comma-separated list of conversation IDs."""
        conversations = [self] + (other_conversations or [])
        return ",".join(conversation.id for conversation in conversations)

    def _fetch(self) -> None:
        data = self._fetch_data()
        other = self._reddit._objector.objectify(data=data)
        self.__dict__.update(other.__dict__)
        super()._fetch()

    def _fetch_info(self) -> tuple[str, dict[str, str], dict[str, bool] | None]:
        return "modmail_conversation", {"id": self.id}, self._info_params

    def archive(self) -> None:
        """Archive the conversation.

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").archive()

        """
        self._reddit.post(API_PATH["modmail_archive"].format(id=self.id))

    def highlight(self) -> None:
        """Highlight the conversation.

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").highlight()

        """
        self._reddit.post(API_PATH["modmail_highlight"].format(id=self.id))

    def mute(self, *, num_days: int = DEFAULT_NUMBER_OF_MUTE_DAYS) -> None:
        """Mute the non-mod user associated with the conversation.

        :param num_days: Duration of mute in days. Valid options are ``3``, ``7``, or
            ``28`` (default: ``3``).

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").mute()

        To mute for 7 days:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").mute(num_days=7)

        """
        params = {"num_hours": num_days * 24} if num_days != self.DEFAULT_NUMBER_OF_MUTE_DAYS else {}
        self._reddit.request(
            method="POST",
            params=params,
            path=API_PATH["modmail_mute"].format(id=self.id),
        )

    def read(self, *, other_conversations: list[ModmailConversation] | None = None) -> None:
        """Mark the conversation(s) as read.

        :param other_conversations: A list of other conversations to mark (default:
            ``None``).

        For example, to mark the conversation as read along with other recent
        conversations from the same user:

        .. code-block:: python

            subreddit = reddit.subreddit("test")
            conversation = subreddit.modmail.conversation("2gmz")
            conversation.read(other_conversations=conversation.user.recent_convos)

        """
        data = {"conversationIds": self._build_conversation_list(other_conversations)}
        self._reddit.post(API_PATH["modmail_read"], data=data)

    def reply(self, *, author_hidden: bool = False, body: str, internal: bool = False) -> ModmailMessage:
        """Reply to the conversation.

        :param author_hidden: When ``True``, author is hidden from non-moderators
            (default: ``False``).
        :param body: The Markdown formatted content for a message.
        :param internal: When ``True``, message is a private moderator note, hidden from
            non-moderators (default: ``False``).

        :returns: A :class:`.ModmailMessage` object for the newly created message.

        For example, to reply to the non-mod user while hiding your username:

        .. code-block:: python

            conversation = reddit.subreddit("test").modmail("2gmz")
            conversation.reply(body="Message body", author_hidden=True)

        To create a private moderator note on the conversation:

        .. code-block:: python

            conversation.reply(body="Message body", internal=True)

        """
        data = {
            "body": body,
            "isAuthorHidden": author_hidden,
            "isInternal": internal,
        }
        response = self._reddit.post(API_PATH["modmail_conversation"].format(id=self.id), data=data)
        if isinstance(response, dict):
            # Reddit recently changed the response format, so we need to handle both in case they change it back
            message_id = response["conversation"]["objIds"][-1]["id"]
            message_data = response["messages"][message_id]
            return self._reddit._objector.objectify(data=message_data)
        for message in response.messages:
            if message.id == response.obj_ids[-1]["id"]:
                break
        return message

    def unarchive(self) -> None:
        """Unarchive the conversation.

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").unarchive()

        """
        self._reddit.post(API_PATH["modmail_unarchive"].format(id=self.id))

    def unhighlight(self) -> None:
        """Un-highlight the conversation.

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").unhighlight()

        """
        self._reddit.delete(API_PATH["modmail_highlight"].format(id=self.id))

    def unmute(self) -> None:
        """Unmute the non-mod user associated with the conversation.

        For example:

        .. code-block:: python

            reddit.subreddit("test").modmail("2gmz").unmute()

        """
        self._reddit.request(method="POST", path=API_PATH["modmail_unmute"].format(id=self.id))

    def unread(self, *, other_conversations: list[ModmailConversation] | None = None) -> None:
        """Mark the conversation(s) as unread.

        :param other_conversations: A list of other conversations to mark (default:
            ``None``).

        For example, to mark the conversation as unread along with other recent
        conversations from the same user:

        .. code-block:: python

            subreddit = reddit.subreddit("test")
            conversation = subreddit.modmail.conversation("2gmz")
            conversation.unread(other_conversations=conversation.user.recent_convos)

        """
        data = {"conversationIds": self._build_conversation_list(other_conversations)}
        self._reddit.post(API_PATH["modmail_unread"], data=data)


class ModmailAction(ModmailObject):
    """A class for moderator actions on modmail conversations."""


class ModmailMessage(ModmailObject):
    """A class for modmail messages."""
