from typing import TYPE_CHECKING, List, Optional, Union

from ...utils.attrs_utils import ClientSerializerMixin, define, field
from ...utils.missing import MISSING
from ..error import LibraryException
from .flags import UserFlags
from .misc import AllowedMentions, File, IDMixin, Snowflake

if TYPE_CHECKING:
    from ...client.models.component import ActionRow, Button, SelectMenu
    from .gw import Presence
    from .message import Attachment, Embed, Message

__all__ = ("User",)


@define()
class User(ClientSerializerMixin, IDMixin):
    """
    A class object representing a user.

    :ivar Snowflake id: The User ID
    :ivar str username: The Username associated (not necessarily unique across the platform)
    :ivar str discriminator: The User's 4-digit discord-tag (i.e.: XXXX)
    :ivar Optional[str] avatar?: The user's avatar hash, if any
    :ivar Optional[bool] bot?: A status denoting if the user is a bot
    :ivar Optional[bool] system?: A status denoting if the user is an Official Discord System user
    :ivar Optional[bool] mfa_enabled?: A status denoting if the user has 2fa on their account
    :ivar Optional[str] banner?: The user's banner hash, if any
    :ivar Optional[str] banner_color?: The user's banner color as a hex, if any
    :ivar Optional[int] accent_color?: The user's banner color as an integer represented of hex color codes
    :ivar Optional[str] locale?: The user's chosen language option
    :ivar Optional[bool] verified?: Whether the email associated with this account has been verified
    :ivar Optional[str] email?: The user's email, if any
    :ivar Optional[UserFlags] flags?: The user's flags
    :ivar Optional[int] premium_type?: The type of Nitro subscription the user has
    :ivar Optional[UserFlags] public_flags?: The user's public flags
    """

    id: Snowflake = field(converter=Snowflake)
    username: str = field(repr=True)
    discriminator: str = field(repr=True)
    avatar: Optional[str] = field(default=None, repr=False)
    bot: Optional[bool] = field(default=None)
    system: Optional[bool] = field(default=None, repr=False)
    mfa_enabled: Optional[bool] = field(default=None)
    banner: Optional[str] = field(default=None, repr=False)
    accent_color: Optional[int] = field(default=None, repr=False)
    banner_color: Optional[str] = field(default=None, repr=False)
    locale: Optional[str] = field(default=None)
    verified: Optional[bool] = field(default=None)
    email: Optional[str] = field(default=None)
    flags: Optional[UserFlags] = field(converter=UserFlags, default=None, repr=False)
    premium_type: Optional[int] = field(default=None, repr=False)
    public_flags: Optional[UserFlags] = field(converter=UserFlags, default=None, repr=False)
    bio: Optional[str] = field(default=None)

    def __str__(self) -> str:
        return self.username

    def has_public_flag(self, flag: Union[UserFlags, int]) -> bool:
        if self.public_flags == 0 or self.public_flags is None:
            return False
        return bool(int(self.public_flags) & flag)

    @property
    def mention(self) -> str:
        """
        Returns a string that allows you to mention the given user.

        :return: The string of the mentioned user.
        :rtype: str
        """
        return f"<@{self.id}>"

    @property
    def avatar_url(self) -> str:
        """
        Returns the URL of the user's avatar

        :return: URL of the user's avatar.
        :rtype: str
        """
        url = "https://cdn.discordapp.com/"
        if self.avatar:
            url += f"avatars/{int(self.id)}/{self.avatar}"
            url += ".gif" if self.avatar.startswith("a_") else ".png"
        else:
            url += f"embed/avatars/{int(self.discriminator) % 5}.png"
        return url

    @property
    def banner_url(self) -> Optional[str]:
        """
        Returns the URL of the user's banner.

        :return: URL of the user's banner (None will be returned if no banner is set)
        :rtype: str
        """
        if not self.banner:
            return None

        url = f"https://cdn.discordapp.com/banners/{int(self.id)}/{self.banner}"
        url += ".gif" if self.banner.startswith("a_") else ".png"
        return url

    @property
    def presence(self) -> Optional["Presence"]:
        """
        Returns the presence of the user.

        :return: Presence of the user (None will be returned if not cached)
        :rtype: Optional[Presence]
        """
        from .gw import Presence

        return self._client.cache[Presence].get(self.id)

    async def send(
        self,
        content: Optional[str] = MISSING,
        *,
        components: Optional[
            Union[
                "ActionRow",
                "Button",
                "SelectMenu",
                List["ActionRow"],
                List["Button"],
                List["SelectMenu"],
            ]
        ] = MISSING,
        tts: Optional[bool] = MISSING,
        attachments: Optional[List["Attachment"]] = MISSING,
        files: Optional[Union[File, List[File]]] = MISSING,
        embeds: Optional[Union["Embed", List["Embed"]]] = MISSING,
        allowed_mentions: Optional[Union[AllowedMentions, dict]] = MISSING,
    ) -> "Message":
        """
        Sends a DM to the user.

        :param content?: The contents of the message as a string or string-converted value.
        :type content?: Optional[str]
        :param components?: A component, or list of components for the message.
        :type components?: Optional[Union[ActionRow, Button, SelectMenu, List[ActionRow], List[Button], List[SelectMenu]]]
        :param tts?: Whether the message utilizes the text-to-speech Discord programme or not.
        :type tts?: Optional[bool]
        :param attachments?: The attachments to attach to the message. Needs to be uploaded to the CDN first
        :type attachments?: Optional[List[Attachment]]
        :param files?: A file or list of files to be attached to the message.
        :type files?: Optional[Union[File, List[File]]]
        :param embeds?: An embed, or list of embeds for the message.
        :type embeds?: Optional[Union[Embed, List[Embed]]]
        :param allowed_mentions?: The allowed mentions for the message.
        :type allowed_mentions?: Optional[Union[AllowedMentions, dict]]
        :return: The sent message as an object.
        :rtype: Message
        """
        if not self._client:
            raise LibraryException(code=13)
        from ...client.models.component import _build_components
        from .message import Message

        _content: str = "" if content is MISSING else content
        _tts: bool = False if tts is MISSING else tts
        _attachments = [] if attachments is MISSING else [a._json for a in attachments]
        _embeds: list = (
            []
            if not embeds or embeds is MISSING
            else ([embed._json for embed in embeds] if isinstance(embeds, list) else [embeds._json])
        )
        _allowed_mentions: dict = (
            {}
            if allowed_mentions is MISSING
            else allowed_mentions._json
            if isinstance(allowed_mentions, AllowedMentions)
            else allowed_mentions
        )
        if not components or components is MISSING:
            _components = []
        else:
            _components = _build_components(components=components)

        if not files or files is MISSING:
            _files = []
        elif isinstance(files, list):
            _files = [file._json_payload(id) for id, file in enumerate(files)]
        else:
            _files = [files._json_payload(0)]
            files = [files]

        _files.extend(_attachments)

        payload = dict(
            content=_content,
            tts=_tts,
            attachments=_files,
            embeds=_embeds,
            components=_components,
            allowed_mentions=_allowed_mentions,
        )
        channel = await self._client.create_dm(recipient_id=int(self.id))
        res = await self._client.create_message(
            channel_id=int(channel["id"]), payload=payload, files=files
        )

        return Message(**res, _client=self._client)
