from datetime import datetime
from typing import Union, List, Optional, AsyncGenerator, BinaryIO

import pyrogram
from pyrogram import raw, enums
from pyrogram import types
from pyrogram import utils
from pyrogram.errors import MessageIdsEmpty
from ..object import Object


class Chat(Object):

    def __init__(
        self,
        *,
        client: "pyrogram.Client" = None,
        id: int,
        type: "enums.ChatType",
        is_verified: bool = None,
        is_restricted: bool = None,
        is_creator: bool = None,
        is_scam: bool = None,
        is_fake: bool = None,
        is_support: bool = None,
        is_forum: bool = None,
        is_participants_hidden: bool = None,
        is_join_request: bool = None,
        is_join_to_send: bool = None,
        is_antispam: bool = None,
        is_slowmode_enabled: bool = None,
        title: str = None,
        username: str = None,
        first_name: str = None,
        last_name: str = None,
        photo: "types.ChatPhoto" = None,
        stories: List["types.Story"] = None,
        wallpaper: "types.Document" = None,
        bio: str = None,
        description: str = None,
        dc_id: int = None,
        folder_id: int = None,
        has_protected_content: bool = None,
        invite_link: str = None,
        pinned_message=None,
        sticker_set_name: str = None,
        can_set_sticker_set: bool = None,
        members_count: int = None,
        slow_mode_delay: int = None,
        restrictions: List["types.Restriction"] = None,
        permissions: "types.ChatPermissions" = None,
        distance: int = None,
        linked_chat: "types.Chat" = None,
        send_as_chat: "types.Chat" = None,
        available_reactions: Optional["types.ChatReactions"] = None,
        usernames: List["types.Username"] = None,
        reply_color: "types.ChatColor" = None,
        profile_color: "types.ChatColor" = None,
        business_info: "types.BusinessInfo" = None,
        birthday: "types.Birthday" = None,
        personal_chat: "types.Chat" = None,
    ):
        super().__init__(client)

        self.id = id
        self.type = type
        self.is_verified = is_verified
        self.is_restricted = is_restricted
        self.is_creator = is_creator
        self.is_scam = is_scam
        self.is_fake = is_fake
        self.is_support = is_support
        self.is_forum = is_forum
        self.is_participants_hidden = is_participants_hidden
        self.is_join_request = is_join_request
        self.is_join_to_send = is_join_to_send
        self.is_antispam = is_antispam
        self.is_slowmode_enabled = is_slowmode_enabled
        self.title = title
        self.username = username
        self.first_name = first_name
        self.last_name = last_name
        self.photo = photo
        self.stories = stories
        self.wallpaper = wallpaper
        self.bio = bio
        self.description = description
        self.dc_id = dc_id
        self.folder_id = folder_id
        self.has_protected_content = has_protected_content
        self.invite_link = invite_link
        self.pinned_message = pinned_message
        self.sticker_set_name = sticker_set_name
        self.can_set_sticker_set = can_set_sticker_set
        self.members_count = members_count
        self.slow_mode_delay = slow_mode_delay
        self.restrictions = restrictions
        self.permissions = permissions
        self.distance = distance
        self.linked_chat = linked_chat
        self.send_as_chat = send_as_chat
        self.available_reactions = available_reactions
        self.usernames = usernames
        self.reply_color = reply_color
        self.profile_color = profile_color
        self.business_info = business_info
        self.birthday = birthday
        self.personal_chat = personal_chat

    @property
    def full_name(self) -> str:
        return (
            " ".join(filter(None, [self.first_name, self.last_name]))
            or self.title
            or None
        )

    @staticmethod
    def _parse_user_chat(client, user: raw.types.User) -> "Chat":
        peer_id = user.id

        return Chat(
            id=peer_id,
            type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE,
            is_verified=getattr(user, "verified", None),
            is_restricted=getattr(user, "restricted", None),
            is_scam=getattr(user, "scam", None),
            is_fake=getattr(user, "fake", None),
            is_support=getattr(user, "support", None),
            username=user.username,
            first_name=user.first_name,
            last_name=user.last_name,
            photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash),
            restrictions=types.List(
                [types.Restriction._parse(r) for r in user.restriction_reason]
            )
            or None,
            dc_id=getattr(getattr(user, "photo", None), "dc_id", None),
            reply_color=types.ChatColor._parse(getattr(user, "color", None)),
            profile_color=types.ChatColor._parse_profile_color(
                getattr(user, "profile_color", None)
            ),
            client=client,
        )

    @staticmethod
    def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
        peer_id = -chat.id
        active_usernames = getattr(chat, "usernames", [])
        usernames = None
        if len(active_usernames) >= 1:
            usernames = []
            for username in active_usernames:
                usernames.append(types.Username._parse(username))

        return Chat(
            id=peer_id,
            type=enums.ChatType.GROUP,
            title=chat.title,
            is_creator=getattr(chat, "creator", None),
            photo=types.ChatPhoto._parse(
                client, getattr(chat, "photo", None), peer_id, 0
            ),
            permissions=types.ChatPermissions._parse(
                getattr(chat, "default_banned_rights", None)
            ),
            members_count=getattr(chat, "participants_count", None),
            dc_id=getattr(getattr(chat, "photo", None), "dc_id", None),
            has_protected_content=getattr(chat, "noforwards", None),
            usernames=usernames,
            client=client,
        )

    @staticmethod
    def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
        peer_id = utils.get_channel_id(
            channel.id if not isinstance(channel, int) else channel
        )
        restriction_reason = getattr(channel, "restriction_reason", [])
        user_name = getattr(channel, "username", None)
        active_usernames = getattr(channel, "usernames", [])
        usernames = None
        if len(active_usernames) >= 1:
            usernames = []
            for username in active_usernames:
                if username.editable:
                    user_name = username.username
                else:
                    usernames.append(types.Username._parse(username))
        if user_name is None and usernames is not None and len(usernames) > 0:
            user_name = usernames[0].username
            usernames.pop(0)

        return Chat(
            id=peer_id,
            type=(
                enums.ChatType.SUPERGROUP
                if getattr(channel, "megagroup", None)
                else enums.ChatType.CHANNEL
            ),
            is_verified=getattr(channel, "verified", None),
            is_restricted=getattr(channel, "restricted", None),
            is_creator=getattr(channel, "creator", None),
            is_scam=getattr(channel, "scam", None),
            is_fake=getattr(channel, "fake", None),
            is_forum=getattr(channel, "forum", None),
            is_join_request=getattr(channel, "join_request", None),
            is_join_to_send=getattr(channel, "join_to_send", None),
            is_slowmode_enabled=getattr(channel, "slowmode_enabled", None),
            title=channel.title,
            username=user_name,
            usernames=usernames,
            photo=types.ChatPhoto._parse(
                client,
                getattr(channel, "photo", None),
                peer_id,
                getattr(channel, "access_hash", 0),
            ),
            restrictions=types.List(
                [types.Restriction._parse(r) for r in restriction_reason]
            )
            or None,
            permissions=types.ChatPermissions._parse(
                getattr(channel, "default_banned_rights", None)
            ),
            members_count=getattr(channel, "participants_count", None),
            dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
            has_protected_content=getattr(channel, "noforwards", None),
            reply_color=types.ChatColor._parse(getattr(channel, "color", None)),
            client=client,
        )

    @staticmethod
    def _parse(
        client,
        message: Union[raw.types.Message, raw.types.MessageService],
        users: dict,
        chats: dict,
        is_chat: bool,
    ) -> "Chat":
        from_id = utils.get_raw_peer_id(message.from_id)
        peer_id = utils.get_raw_peer_id(message.peer_id)
        chat_id = (peer_id or from_id) if is_chat else (from_id or peer_id)

        if isinstance(message.peer_id, raw.types.PeerUser):
            return Chat._parse_user_chat(client, users[chat_id])

        if isinstance(message.peer_id, raw.types.PeerChat):
            return Chat._parse_chat_chat(client, chats[chat_id])

        return Chat._parse_channel_chat(client, chats[chat_id])

    @staticmethod
    def _parse_dialog(client, peer, users: dict, chats: dict):
        if isinstance(peer, raw.types.PeerUser):
            return Chat._parse_user_chat(client, users[peer.user_id])
        elif isinstance(peer, raw.types.PeerChat):
            return Chat._parse_chat_chat(client, chats[peer.chat_id])
        else:
            return Chat._parse_channel_chat(client, chats[peer.channel_id])

    @staticmethod
    async def _parse_full(
        client, chat_full: Union[raw.types.messages.ChatFull, raw.types.users.UserFull]
    ) -> "Chat":
        users = {u.id: u for u in chat_full.users}
        chats = {c.id: c for c in chat_full.chats}

        if isinstance(chat_full, raw.types.users.UserFull):
            full_user = chat_full.full_user

            parsed_chat = Chat._parse_user_chat(client, users[full_user.id])
            parsed_chat.bio = full_user.about
            parsed_chat.folder_id = getattr(full_user, "folder_id", None)
            parsed_chat.business_info = types.BusinessInfo._parse(
                client, full_user, users
            )
            birthday = getattr(full_user, "birthday", None)
            parsed_chat.birthday = (
                types.Birthday._parse(birthday) if birthday is not None else None
            )
            personal_chat_id = getattr(full_user, "personal_channel_id", None)
            if personal_chat_id is not None:
                personal_chat = await client.invoke(
                    raw.functions.channels.GetChannels(
                        id=[
                            await client.resolve_peer(
                                utils.get_channel_id(personal_chat_id)
                            )
                        ]
                    )
                )
                parsed_chat.personal_chat = Chat._parse_chat(
                    client, personal_chat.chats[0]
                )

            if full_user.pinned_msg_id:
                try:
                    parsed_chat.pinned_message = await client.get_messages(
                        chat_id=parsed_chat.id, message_ids=full_user.pinned_msg_id
                    )
                except MessageIdsEmpty:
                    parsed_chat.pinned_message = types.Message(
                        id=full_user.pinned_msg_id, empty=True, client=client
                    )

            if getattr(full_user, "stories"):
                peer_stories: raw.types.PeerStories = full_user.stories
                parsed_chat.stories = (
                    types.List(
                        [
                            await types.Story._parse(client, story, peer_stories.peer)
                            for story in peer_stories.stories
                        ]
                    )
                    or None
                )

            if getattr(full_user, "wallpaper") and isinstance(
                full_user.wallpaper, raw.types.WallPaper
            ):
                parsed_chat.wallpaper = types.Document._parse(
                    client, full_user.wallpaper.document, "wallpaper.jpg"
                )
        else:
            full_chat = chat_full.full_chat
            chat_raw = chats[full_chat.id]

            if isinstance(full_chat, raw.types.ChatFull):
                parsed_chat = Chat._parse_chat_chat(client, chat_raw)
                parsed_chat.description = full_chat.about or None

                if isinstance(full_chat.participants, raw.types.ChatParticipants):
                    parsed_chat.members_count = len(full_chat.participants.participants)
            else:
                parsed_chat = Chat._parse_channel_chat(client, chat_raw)
                parsed_chat.members_count = full_chat.participants_count
                parsed_chat.slow_mode_delay = getattr(
                    full_chat, "slowmode_seconds", None
                )
                parsed_chat.description = full_chat.about or None
                # TODO: Add StickerSet type
                parsed_chat.can_set_sticker_set = full_chat.can_set_stickers
                parsed_chat.sticker_set_name = getattr(
                    full_chat.stickerset, "short_name", None
                )
                parsed_chat.is_participants_hidden = full_chat.participants_hidden
                parsed_chat.is_antispam = full_chat.antispam
                parsed_chat.folder_id = getattr(full_chat, "folder_id", None)

                linked_chat_raw = chats.get(full_chat.linked_chat_id, None)

                if linked_chat_raw:
                    parsed_chat.linked_chat = Chat._parse_channel_chat(
                        client, linked_chat_raw
                    )

                default_send_as = full_chat.default_send_as

                if default_send_as:
                    if isinstance(default_send_as, raw.types.PeerUser):
                        send_as_raw = users[default_send_as.user_id]
                    else:
                        send_as_raw = chats[default_send_as.channel_id]

                    parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw)

                if getattr(full_chat, "stories"):
                    peer_stories: raw.types.PeerStories = full_chat.stories
                    parsed_chat.stories = (
                        types.List(
                            [
                                await types.Story._parse(
                                    client, story, peer_stories.peer
                                )
                                for story in peer_stories.stories
                            ]
                        )
                        or None
                    )

                if getattr(full_chat, "wallpaper") and isinstance(
                    full_chat.wallpaper, raw.types.WallPaper
                ):
                    parsed_chat.wallpaper = types.Document._parse(
                        client, full_chat.wallpaper.document, "wallpaper.jpg"
                    )

            if full_chat.pinned_msg_id:
                parsed_chat.pinned_message = await client.get_messages(
                    chat_id=parsed_chat.id, message_ids=full_chat.pinned_msg_id
                )

            if isinstance(full_chat.exported_invite, raw.types.ChatInviteExported):
                parsed_chat.invite_link = full_chat.exported_invite.link

            parsed_chat.available_reactions = types.ChatReactions._parse(
                client, full_chat.available_reactions
            )

        return parsed_chat

    @staticmethod
    def _parse_chat(
        client, chat: Union[raw.types.Chat, raw.types.User, raw.types.Channel]
    ) -> "Chat":
        if isinstance(chat, (raw.types.Chat, raw.types.ChatForbidden)):
            return Chat._parse_chat_chat(client, chat)
        elif isinstance(chat, raw.types.User):
            return Chat._parse_user_chat(client, chat)
        else:
            return Chat._parse_channel_chat(client, chat)

    def listen(self, *args, **kwargs):
        return self._client.listen(*args, chat_id=self.id, **kwargs)

    def ask(self, text, *args, **kwargs):
        return self._client.ask(self.id, text, *args, **kwargs)

    def stop_listening(self, *args, **kwargs):
        return self._client.stop_listening(*args, chat_id=self.id, **kwargs)

    async def archive(self):
        return await self._client.archive_chats(self.id)

    async def unarchive(self):
        return await self._client.unarchive_chats(self.id)

    # TODO: Remove notes about "All Members Are Admins" for basic groups, the attribute doesn't exist anymore
    async def set_title(self, title: str) -> bool:
        return await self._client.set_chat_title(chat_id=self.id, title=title)

    async def set_description(self, description: str) -> bool:
        return await self._client.set_chat_description(
            chat_id=self.id, description=description
        )

    async def set_photo(
        self,
        *,
        photo: Union[str, BinaryIO] = None,
        video: Union[str, BinaryIO] = None,
        video_start_ts: float = None,
    ) -> bool:
        return await self._client.set_chat_photo(
            chat_id=self.id, photo=photo, video=video, video_start_ts=video_start_ts
        )

    async def ban_member(
        self, user_id: Union[int, str], until_date: datetime = utils.zero_datetime()
    ) -> Union["types.Message", bool]:
        return await self._client.ban_chat_member(
            chat_id=self.id, user_id=user_id, until_date=until_date
        )

    async def unban_member(self, user_id: Union[int, str]) -> bool:
        return await self._client.unban_chat_member(
            chat_id=self.id,
            user_id=user_id,
        )

    async def restrict_member(
        self,
        user_id: Union[int, str],
        permissions: "types.ChatPermissions",
        until_date: datetime = utils.zero_datetime(),
    ) -> "types.Chat":
        return await self._client.restrict_chat_member(
            chat_id=self.id,
            user_id=user_id,
            permissions=permissions,
            until_date=until_date,
        )

    async def promote_member(
        self,
        user_id: Union[int, str],
        privileges: "types.ChatPrivileges" = None,
    ) -> bool:
        return await self._client.promote_chat_member(
            chat_id=self.id, user_id=user_id, privileges=privileges
        )

    async def join(self):
        return await self._client.join_chat(self.username or self.id)

    async def leave(self):
        return await self._client.leave_chat(self.id)

    async def export_invite_link(self):
        return await self._client.export_chat_invite_link(self.id)

    async def get_member(
        self,
        user_id: Union[int, str],
    ) -> "types.ChatMember":
        return await self._client.get_chat_member(self.id, user_id=user_id)

    def get_members(
        self,
        query: str = "",
        limit: int = 0,
        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH,
    ) -> Optional[AsyncGenerator["types.ChatMember", None]]:
        return self._client.get_chat_members(
            self.id, query=query, limit=limit, filter=filter
        )

    async def add_members(
        self,
        user_ids: Union[Union[int, str], List[Union[int, str]]],
        forward_limit: int = 100,
    ) -> bool:
        return await self._client.add_chat_members(
            self.id, user_ids=user_ids, forward_limit=forward_limit
        )

    async def mark_unread(
        self,
    ) -> bool:
        return await self._client.mark_chat_unread(self.id)

    async def set_protected_content(self, enabled: bool) -> bool:
        return await self._client.set_chat_protected_content(self.id, enabled=enabled)

    async def unpin_all_messages(self) -> bool:
        return await self._client.unpin_all_chat_messages(self.id)
