from __future__ import annotations

import datetime
from typing import TYPE_CHECKING, Any, Optional


from .utils import _Missing, Missing

from .asset import Asset
from .permissions import Permissions
from .permissions_calculator import calculate_permissions
from .user import User
from .file import File

if TYPE_CHECKING:
    from .channel import Channel
    from .server import Server
    from .state import State
    from .types import File as FilePayload
    from .types import Member as MemberPayload
    from .role import Role

__all__ = ("Member",)

def flattern_user(member: Member, user: User) -> None:
    for attr in user.__flattern_attributes__:
        setattr(member, attr, getattr(user, attr))

class Member(User):
    """Represents a member of a server, subclasses :class:`User`

    Attributes
    -----------
    nickname: Optional[:class:`str`]
        The nickname of the member if any
    roles: list[:class:`Role`]
        The roles of the member, ordered by the role's rank in decending order
    server: :class:`Server`
        The server the member belongs to
    guild_avatar: Optional[:class:`Asset`]
        The member's guild avatar if any
    """
    __slots__ = ("state", "nickname", "roles", "server", "guild_avatar", "joined_at", "current_timeout")

    def __init__(self, data: MemberPayload, server: Server, state: State):
        user = state.get_user(data["_id"]["user"])

        # due to not having a user payload and only a user object we have to manually add all the attributes instead of calling User.__init__
        flattern_user(self, user)
        user._members[server.id] = self

        self.state: State = state

        self.guild_avatar: Asset | None

        if avatar := data.get("avatar"):
            self.guild_avatar = Asset(avatar, state)
        else:
            self.guild_avatar = None

        roles = [server.get_role(role_id) for role_id in data.get("roles", [])]
        self.roles: list[Role] = sorted(roles, key=lambda role: role.rank, reverse=True)

        self.server: Server = server
        self.nickname: str | None = data.get("nickname")
        joined_at = data["joined_at"]

        if isinstance(joined_at, int):
            self.joined_at: datetime.datetime = datetime.datetime.fromtimestamp(joined_at / 1000)
        else:
            self.joined_at: datetime.datetime = datetime.datetime.strptime(joined_at, "%Y-%m-%dT%H:%M:%S.%f%z")

        self.current_timeout: datetime.datetime | None

        if current_timeout := data.get("timeout"):
            self.current_timeout = datetime.datetime.strptime(current_timeout, "%Y-%m-%dT%H:%M:%S.%f%z")
        else:
            self.current_timeout = None

    @property
    def avatar(self) -> Optional[Asset]:
        """Optional[:class:`Asset`] The avatar the member is displaying, this includes guild avatars and masqueraded avatar"""
        return self.masquerade_avatar or self.guild_avatar or self.original_avatar

    @property
    def name(self) -> str:
        """:class:`str` The name the user is displaying, this includes (in order) their masqueraded name, display name and orginal name"""
        return self.nickname or self.display_name or self.masquerade_name or self.original_name

    @property
    def mention(self) -> str:
        """:class:`str`: Returns a string that allows you to mention the given member."""
        return f"<@{self.id}>"

    def _update(self, *, nickname: Optional[str] = None, avatar: Optional[FilePayload] = None, roles: Optional[list[str]] = None):
        if nickname is not None:
            self.nickname = nickname

        if avatar is not None:
            self.guild_avatar = Asset(avatar, self.state)

        if roles is not None:
            member_roles = [self.server.get_role(role_id) for role_id in roles]
            self.roles = sorted(member_roles, key=lambda role: role.rank, reverse=True)

    async def kick(self) -> None:
        """Kicks the member from the server"""
        await self.state.http.kick_member(self.server.id, self.id)

    async def ban(self, *, reason: Optional[str] = None) -> None:
        """Bans the member from the server

        Parameters
        -----------
        reason: Optional[:class:`str`]
            The reason for the ban
        """
        await self.state.http.ban_member(self.server.id, self.id, reason)

    async def unban(self) -> None:
        """Unbans the member from the server"""
        await self.state.http.unban_member(self.server.id, self.id)

    async def edit(
        self,
        *,
        nickname: str | None | _Missing = Missing,
        roles: list[Role] | None | _Missing = Missing,
        avatar: File | None | _Missing = Missing,
        timeout: datetime.timedelta | None | _Missing = Missing
    ) -> None:
        remove: list[str] = []
        data: dict[str, Any] = {}

        if nickname is None:
            remove.append("Nickname")
        elif nickname is not Missing:
            data["nickname"] = nickname

        if roles is None:
            remove.append("Roles")
        elif roles is not Missing:
            data["roles"] = roles

        if avatar is None:
            remove.append("Avatar")
        elif avatar is not Missing:
            # pyright cant understand custom singletons - it doesnt know this will never be an instance of _Missing here because Missing is the only instance
            assert not isinstance(avatar, _Missing)

            data["avatar"] = (await self.state.http.upload_file(avatar, "avatars"))["id"]

        if timeout is None:
            remove.append("Timeout")
        elif timeout is not Missing:
            assert not isinstance(timeout, _Missing)
            data["timeout"] = (datetime.datetime.now(datetime.timezone.utc) + timeout).isoformat()

        await self.state.http.edit_member(self.server.id, self.id, remove, data)

    async def timeout(self, length: datetime.timedelta) -> None:
        """Timeouts the member

        Parameters
        -----------
        length: :class:`datetime.timedelta`
            The length of the timeout
        """
        ends_at = datetime.datetime.utcnow() + length

        await self.state.http.edit_member(self.server.id, self.id, None, {"timeout": ends_at.isoformat()})

    def get_permissions(self) -> Permissions:
        """Gets the permissions for the member in the server

        Returns
        --------
        :class:`Permissions`
            The members permissions
        """
        return calculate_permissions(self, self.server)

    def get_channel_permissions(self, channel: Channel) -> Permissions:
        """Gets the permissions for the member in the server taking into account the channel as well

        Parameters
        -----------
        channel: :class:`Channel`
            The channel to calculate permissions with

        Returns
        --------
        :class:`Permissions`
            The members permissions
        """
        return calculate_permissions(self, channel)

    def has_permissions(self, **permissions: bool) -> bool:
        """Computes if the member has the specified permissions

        Parameters
        -----------
        permissions: :class:`bool`
            The permissions to check, this also accepted `False` if you need to check if the member does not have the permission

        Returns
        --------
        :class:`bool`
            Whether or not they have the permissions
        """
        calculated_perms = self.get_permissions()

        return all([getattr(calculated_perms, key, False) == value for key, value in permissions.items()])

    def has_channel_permissions(self, channel: Channel, **permissions: bool) -> bool:
        """Computes if the member has the specified permissions, taking into account the channel as well

        Parameters
        -----------
        channel: :class:`Channel`
            The channel to calculate permissions with
        permissions: :class:`bool`
            The permissions to check, this also accepted `False` if you need to check if the member does not have the permission

        Returns
        --------
        :class:`bool`
            Whether or not they have the permissions
        """
        calculated_perms = self.get_channel_permissions(channel)

        return all([getattr(calculated_perms, key, False) == value for key, value in permissions.items()])
