# SPDX-License-Identifier: MIT

from __future__ import annotations

from typing import TYPE_CHECKING, Any, List, NoReturn, Optional

from .guild import Guild, Member
from .utils import MISSING, _assetbytes_to_base64_data, parse_time

__all__ = ("Template",)

if TYPE_CHECKING:
    import datetime

    from .asset import AssetBytes
    from .flags import MemberCacheFlags
    from .state import ConnectionState
    from .types.emoji import Emoji as EmojiPayload
    from .types.template import Template as TemplatePayload
    from .user import ClientUser, User


class _FriendlyHttpAttributeErrorHelper:
    __slots__ = ()

    def __getattr__(self, attr) -> NoReturn:
        raise AttributeError("PartialTemplateState does not support http methods.")


class _PartialTemplateState:
    def __init__(self, *, state: ConnectionState) -> None:
        self.__state: ConnectionState = state
        self.http = _FriendlyHttpAttributeErrorHelper()

    @property
    def shard_count(self) -> Optional[int]:
        return self.__state.shard_count

    @property
    def user(self) -> ClientUser:
        return self.__state.user

    @property
    def self_id(self) -> int:
        return self.__state.user.id

    @property
    def member_cache_flags(self) -> MemberCacheFlags:
        return self.__state.member_cache_flags

    def store_emoji(self, guild: Guild, data: EmojiPayload) -> None:
        return None

    def _get_voice_client(self, id: int) -> None:
        return None

    def _get_message(self, id: int) -> None:
        return None

    def _get_guild(self, id) -> Optional[Guild]:
        return self.__state._get_guild(id)

    async def query_members(self, **kwargs: Any) -> List[Member]:
        return []

    def __getattr__(self, attr) -> NoReturn:
        raise AttributeError(f"PartialTemplateState does not support {attr!r}.")


class Template:
    """Represents a Discord template.

    .. versionadded:: 1.4

    Attributes
    ----------
    code: :class:`str`
        The template code.
    uses: :class:`int`
        How many times the template has been used.
    name: :class:`str`
        The name of the template.
    description: :class:`str`
        The description of the template.
    creator: :class:`User`
        The creator of the template.
    created_at: :class:`datetime.datetime`
        An aware datetime in UTC representing when the template was created.
    updated_at: :class:`datetime.datetime`
        An aware datetime in UTC representing when the template was last updated.
        This is referred to as "last synced" in the official Discord client.
    source_guild: :class:`Guild`
        The source guild.
    is_dirty: Optional[:class:`bool`]
        Whether the template has unsynced changes.

        .. versionadded:: 2.0
    """

    __slots__ = (
        "code",
        "uses",
        "name",
        "description",
        "creator",
        "created_at",
        "updated_at",
        "source_guild",
        "is_dirty",
        "_state",
    )

    def __init__(self, *, state: ConnectionState, data: TemplatePayload) -> None:
        self._state = state
        self._store(data)

    def _store(self, data: TemplatePayload) -> None:
        self.code: str = data["code"]
        self.uses: int = data["usage_count"]
        self.name: str = data["name"]
        self.description: Optional[str] = data["description"]
        creator_data = data.get("creator")
        self.creator: Optional[User] = (
            None if creator_data is None else self._state.create_user(creator_data)
        )

        self.created_at: Optional[datetime.datetime] = parse_time(data.get("created_at"))
        self.updated_at: Optional[datetime.datetime] = parse_time(data.get("updated_at"))

        guild_id = int(data["source_guild_id"])
        guild: Optional[Guild] = self._state._get_guild(guild_id)

        self.source_guild: Guild
        if guild is None:
            source_serialised = data["serialized_source_guild"]
            source_serialised["id"] = guild_id
            state = _PartialTemplateState(state=self._state)
            # Guild expects a ConnectionState, we're passing a _PartialTemplateState
            self.source_guild = Guild(data=source_serialised, state=state)  # type: ignore
        else:
            self.source_guild = guild

        self.is_dirty: Optional[bool] = data.get("is_dirty", None)

    def __repr__(self) -> str:
        return (
            f"<Template code={self.code!r} uses={self.uses} name={self.name!r}"
            f" creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>"
        )

    async def create_guild(self, name: str, icon: Optional[AssetBytes] = None) -> Guild:
        """|coro|

        Creates a :class:`.Guild` using the template.

        Bot accounts in more than 10 guilds are not allowed to create guilds.

        .. versionchanged:: 2.5
            Removed the ``region`` parameter.

        .. versionchanged:: 2.6
            Raises :exc:`ValueError` instead of ``InvalidArgument``.

        Parameters
        ----------
        name: :class:`str`
            The name of the guild.
        icon: Optional[|resource_type|]
            The icon of the guild.
            See :meth:`.ClientUser.edit` for more details on what is expected.

            .. versionchanged:: 2.5
                Now accepts various resource types in addition to :class:`bytes`.


        Raises
        ------
        NotFound
            The ``icon`` asset couldn't be found.
        HTTPException
            Guild creation failed.
        TypeError
            The ``icon`` asset is a lottie sticker (see :func:`Sticker.read`).
        ValueError
            Invalid icon image format given. Must be PNG or JPG.

        Returns
        -------
        :class:`.Guild`
            The guild created. This is not the same guild that is
            added to cache.
        """
        icon_data = await _assetbytes_to_base64_data(icon)

        data = await self._state.http.create_from_template(self.code, name, icon_data)
        return Guild(data=data, state=self._state)

    async def sync(self) -> Template:
        """|coro|

        Syncs the template to the guild's current state.

        You must have the :attr:`~Permissions.manage_guild` permission in the
        source guild to do this.

        .. versionadded:: 1.7

        .. versionchanged:: 2.0
            The template is no longer edited in-place, instead it is returned.

        Raises
        ------
        HTTPException
            Editing the template failed.
        Forbidden
            You don't have permissions to edit the template.
        NotFound
            This template does not exist.

        Returns
        -------
        :class:`Template`
            The newly edited template.
        """
        data = await self._state.http.sync_template(self.source_guild.id, self.code)
        return Template(state=self._state, data=data)

    async def edit(
        self,
        *,
        name: str = MISSING,
        description: Optional[str] = MISSING,
    ) -> Template:
        """|coro|

        Edits the template metadata.

        You must have the :attr:`~Permissions.manage_guild` permission in the
        source guild to do this.

        .. versionadded:: 1.7

        .. versionchanged:: 2.0
            The template is no longer edited in-place, instead it is returned.

        Parameters
        ----------
        name: :class:`str`
            The template's new name.
        description: Optional[:class:`str`]
            The template's new description.

        Raises
        ------
        HTTPException
            Editing the template failed.
        Forbidden
            You don't have permissions to edit the template.
        NotFound
            This template does not exist.

        Returns
        -------
        :class:`Template`
            The newly edited template.
        """
        payload = {}

        if name is not MISSING:
            payload["name"] = name
        if description is not MISSING:
            payload["description"] = description

        data = await self._state.http.edit_template(self.source_guild.id, self.code, payload)
        return Template(state=self._state, data=data)

    async def delete(self) -> None:
        """|coro|

        Deletes the template.

        You must have the :attr:`~Permissions.manage_guild` permission in the
        source guild to do this.

        .. versionadded:: 1.7

        Raises
        ------
        HTTPException
            Editing the template failed.
        Forbidden
            You don't have permissions to edit the template.
        NotFound
            This template does not exist.
        """
        await self._state.http.delete_template(self.source_guild.id, self.code)

    @property
    def url(self) -> str:
        """:class:`str`: The template url.

        .. versionadded:: 2.0
        """
        return f"https://discord.new/{self.code}"
