from __future__ import annotations

import html
from typing import TYPE_CHECKING

import pyrogram
from pyrogram import enums, raw, types, utils
from pyrogram.types.object import Object
from pyrogram.types.update import Update

if TYPE_CHECKING:
    from datetime import datetime


class Link(str):
    HTML = "<a href={url}>{text}</a>"
    MARKDOWN = "[{text}]({url})"

    def __init__(self, url: str, text: str, style: enums.ParseMode) -> None:
        super().__init__()

        self.url = url
        self.text = text
        self.style = style

    @staticmethod
    def format(url: str, text: str, style: enums.ParseMode):
        fmt = Link.MARKDOWN if style == enums.ParseMode.MARKDOWN else Link.HTML

        return fmt.format(url=url, text=html.escape(text))

    def __new__(cls, url, text, style):
        return str.__new__(cls, Link.format(url, text, style))

    def __call__(self, other: str | None = None, *, style: str | None = None):
        return Link.format(self.url, other or self.text, style or self.style)

    def __str__(self) -> str:
        return Link.format(self.url, self.text, self.style)


class User(Object, Update):
    """A Telegram user or bot.

    Parameters:
        id (``int``):
            Unique identifier for this user or bot.

        is_self(``bool``, *optional*):
            True, if this user is you yourself.

        is_contact(``bool``, *optional*):
            True, if this user is in your contacts.

        is_mutual_contact(``bool``, *optional*):
            True, if you both have each other's contact.

        is_deleted(``bool``, *optional*):
            True, if this user is deleted.

        is_bot (``bool``, *optional*):
            True, if this user is a bot.

        is_verified (``bool``, *optional*):
            True, if this user has been verified by Telegram.

        is_restricted (``bool``, *optional*):
            True, if this user has been restricted. Bots only.
            See *restriction_reason* for details.

        is_scam (``bool``, *optional*):
            True, if this user has been flagged for scam.

        is_fake (``bool``, *optional*):
            True, if this user has been flagged for impersonation.

        is_support (``bool``, *optional*):
            True, if this user is part of the Telegram support team.

        is_premium (``bool``, *optional*):
            True, if this user is a premium user.

        is_contacts_only (``bool``, *optional*):
            True, if this user is only allow incoming message from users in their contacts/premium users.

        is_bot_business (``bool``, *optional*):
            True, if this bot can connect to business account.

        first_name (``str``, *optional*):
            User's or bot's first name.

        last_name (``str``, *optional*):
            User's or bot's last name.

        status (:obj:`~pyrogram.enums.UserStatus`, *optional*):
            User's last seen & online status. ``None``, for bots.

        last_online_date (:py:obj:`~datetime.datetime`, *optional*):
            Last online date of a user. Only available in case status is :obj:`~pyrogram.enums.UserStatus.OFFLINE`.

        next_offline_date (:py:obj:`~datetime.datetime`, *optional*):
            Date when a user will automatically go offline. Only available in case status is :obj:`~pyrogram.enums.UserStatus.ONLINE`.

        username (``str``, *optional*):
            User's or bot's username.

        usernames (List of :obj:`~pyrogram.types.Username`, *optional*):
            List of all chat (fragment) usernames; for private chats, supergroups and channels.
            Returned only in :meth:`~pyrogram.Client.get_chat`.

        language_code (``str``, *optional*):
            IETF language tag of the user's language.

        emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*):
            Emoji status.

        dc_id (``int``, *optional*):
            User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo.
            Note that this information is approximate; it is based on where Telegram stores a user profile pictures and
            does not by any means tell you the user location (i.e. a user might travel far away, but will still connect
            to its assigned DC). More info at `FAQs </faq#what-are-the-ip-addresses-of-telegram-data-centers>`_.

        phone_number (``str``, *optional*):
            User's phone number.

        photo (:obj:`~pyrogram.types.ChatPhoto`, *optional*):
            User's or bot's current profile photo. Suitable for downloads only.

        restrictions (List of :obj:`~pyrogram.types.Restriction`, *optional*):
            The list of reasons why this bot might be unavailable to some users.
            This field is available only in case *is_restricted* is True.

        full_name (``str``, *optional*):
            User's or bot's full name.

        mention (``str``, *property*):
            Generate a text mention for this user.
            You can use ``user.mention()`` to mention the user using their first name (styled using html), or
            ``user.mention("another name")`` for a custom name. To choose a different style
            ("html" or "md"/"markdown") use ``user.mention(style="md")``.

        reply_color (:obj:`~pyrogram.types.ChatColor`, *optional*)
            Chat reply color.

        profile_color (:obj:`~pyrogram.types.ChatColor`, *optional*)
            Chat profile color.

        active_users (``int``, *optional*):
            Bot's active users count.
    """

    def __init__(
        self,
        *,
        client: pyrogram.Client = None,
        id: int,
        is_self: bool | None = None,
        is_contact: bool | None = None,
        is_mutual_contact: bool | None = None,
        is_deleted: bool | None = None,
        is_bot: bool | None = None,
        is_verified: bool | None = None,
        is_restricted: bool | None = None,
        is_scam: bool | None = None,
        is_fake: bool | None = None,
        is_support: bool | None = None,
        is_premium: bool | None = None,
        is_contacts_only: bool | None = None,
        is_bot_business: bool | None = None,
        first_name: str | None = None,
        last_name: str | None = None,
        status: enums.UserStatus = None,
        last_online_date: datetime | None = None,
        next_offline_date: datetime | None = None,
        username: str | None = None,
        usernames: list[types.Username] | None = None,
        language_code: str | None = None,
        emoji_status: types.EmojiStatus | None = None,
        dc_id: int | None = None,
        phone_number: str | None = None,
        photo: types.ChatPhoto = None,
        restrictions: list[types.Restriction] | None = None,
        reply_color: types.ChatColor = None,
        profile_color: types.ChatColor = None,
        active_users: int | None = None,
    ) -> None:
        super().__init__(client)

        self.id = id
        self.is_self = is_self
        self.is_contact = is_contact
        self.is_mutual_contact = is_mutual_contact
        self.is_deleted = is_deleted
        self.is_bot = is_bot
        self.is_verified = is_verified
        self.is_restricted = is_restricted
        self.is_scam = is_scam
        self.is_fake = is_fake
        self.is_support = is_support
        self.is_premium = is_premium
        self.is_contacts_only = is_contacts_only
        self.is_bot_business = is_bot_business
        self.first_name = first_name
        self.last_name = last_name
        self.status = status
        self.last_online_date = last_online_date
        self.next_offline_date = next_offline_date
        self.username = username
        self.usernames = usernames
        self.language_code = language_code
        self.emoji_status = emoji_status
        self.dc_id = dc_id
        self.phone_number = phone_number
        self.photo = photo
        self.restrictions = restrictions
        self.reply_color = reply_color
        self.profile_color = profile_color
        self.active_users = active_users

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

    @property
    def mention(self):
        return Link(
            f"tg://user?id={self.id}",
            self.first_name or "Deleted Account",
            self._client.parse_mode,
        )

    @staticmethod
    def _parse(client, user: raw.base.User) -> User | None:
        if user is None or isinstance(user, raw.types.UserEmpty):
            return None
        user_name = user.username
        active_usernames = getattr(user, "usernames", [])
        active_users = getattr(user, "bot_active_users", None)
        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 User(
            id=user.id,
            is_self=user.is_self,
            is_contact=user.contact,
            is_mutual_contact=user.mutual_contact,
            is_deleted=user.deleted,
            is_bot=user.bot,
            is_verified=user.verified,
            is_restricted=user.restricted,
            is_scam=user.scam,
            is_fake=user.fake,
            is_support=user.support,
            is_premium=user.premium,
            is_contacts_only=user.contact_require_premium,
            is_bot_business=user.bot_business,
            first_name=user.first_name,
            last_name=user.last_name,
            **User._parse_status(user.status, user.bot),
            username=user_name,
            usernames=usernames,
            language_code=user.lang_code,
            emoji_status=types.EmojiStatus._parse(client, user.emoji_status),
            dc_id=getattr(user.photo, "dc_id", None),
            phone_number=user.phone,
            photo=types.ChatPhoto._parse(
                client,
                user.photo,
                user.id,
                user.access_hash,
            ),
            restrictions=types.List(
                [types.Restriction._parse(r) for r in user.restriction_reason],
            )
            or None,
            reply_color=types.ChatColor._parse(getattr(user, "color", None)),
            profile_color=types.ChatColor._parse_profile_color(
                getattr(user, "profile_color", None),
            ),
            active_users=active_users,
            client=client,
        )

    @staticmethod
    def _parse_status(user_status: raw.base.UserStatus, is_bot: bool = False):
        if isinstance(user_status, raw.types.UserStatusOnline):
            status, date = (
                enums.UserStatus.ONLINE,
                user_status.expires,
            )
        elif isinstance(user_status, raw.types.UserStatusOffline):
            status, date = (
                enums.UserStatus.OFFLINE,
                user_status.was_online,
            )
        elif isinstance(user_status, raw.types.UserStatusRecently):
            status, date = enums.UserStatus.RECENTLY, None
        elif isinstance(user_status, raw.types.UserStatusLastWeek):
            status, date = enums.UserStatus.LAST_WEEK, None
        elif isinstance(user_status, raw.types.UserStatusLastMonth):
            status, date = enums.UserStatus.LAST_MONTH, None
        else:
            status, date = enums.UserStatus.LONG_AGO, None

        last_online_date = None
        next_offline_date = None

        if is_bot:
            status = None

        if status == enums.UserStatus.ONLINE:
            next_offline_date = utils.timestamp_to_datetime(date)

        if status == enums.UserStatus.OFFLINE:
            last_online_date = utils.timestamp_to_datetime(date)

        return {
            "status": status,
            "last_online_date": last_online_date,
            "next_offline_date": next_offline_date,
        }

    @staticmethod
    def _parse_user_status(client, user_status: raw.types.UpdateUserStatus):
        return User(
            id=user_status.user_id,
            **User._parse_status(user_status.status),
            client=client,
        )

    def listen(self, *args, **kwargs):
        """Bound method *listen* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            client.wait_for_message(user_id)

        Parameters:
            args (*optional*):
                The arguments to pass to the :meth:`~pyrogram.Client.listen` method.

            kwargs (*optional*):
                The keyword arguments to pass to the :meth:`~pyrogram.Client.listen` method.

        Example:
            .. code-block:: python

                user.listen()

        Returns:
            :obj:`~pyrogram.types.Message`: On success, the reply message is returned.
        Raises:
            RPCError: In case of a Telegram RPC error.
            asyncio.TimeoutError: In case reply not received within the timeout.
        """
        return self._client.listen(*args, user_id=self.id, **kwargs)

    def ask(self, text, *args, **kwargs):
        """Bound method *ask* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            client.send_message(user_id, "What is your name?")

            client.wait_for_message(user_id)

        Parameters:
            text (``str``):
                Text of the message to be sent.

            args:
                The arguments to pass to the :meth:`~pyrogram.Client.listen` method.

            kwargs:
                The keyword arguments to pass to the :meth:`~pyrogram.Client.listen` method.

        Example:
            .. code-block:: python

                user.ask("What is your name?")

        Returns:
            :obj:`~pyrogram.types.Message`: On success, the reply message is returned.
        Raises:
            RPCError: In case of a Telegram RPC error.
            asyncio.TimeoutError: In case reply not received within the timeout.
        """
        return self._client.ask(self.id, text, *args, user_id=self.id, **kwargs)

    def stop_listening(self, *args, **kwargs):
        """Bound method *stop_listening* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            client.stop_listening(user_id=user_id)

        Parameters:
            args (*optional*):
                The arguments to pass to the :meth:`~pyrogram.Client.listen` method.

            kwargs (*optional*):
                The keyword arguments to pass to the :meth:`~pyrogram.Client.listen` method.

        Example:
            .. code-block:: python

                user.stop_listen()
        """
        return self._client.stop_listening(*args, user_id=self.id, **kwargs)

    async def archive(self):
        """Bound method *archive* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            await client.archive_chats(123456789)

        Example:
            .. code-block:: python

               await user.archive()

        Returns:
            True on success.

        Raises:
            RPCError: In case of a Telegram RPC error.
        """

        return await self._client.archive_chats(self.id)

    async def unarchive(self):
        """Bound method *unarchive* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            await client.unarchive_chats(123456789)

        Example:
            .. code-block:: python

                await user.unarchive()

        Returns:
            True on success.

        Raises:
            RPCError: In case of a Telegram RPC error.
        """

        return await self._client.unarchive_chats(self.id)

    def block(self):
        """Bound method *block* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            await client.block_user(123456789)

        Example:
            .. code-block:: python

                await user.block()

        Returns:
            True on success.

        Raises:
            RPCError: In case of a Telegram RPC error.
        """

        return self._client.block_user(self.id)

    def unblock(self):
        """Bound method *unblock* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            client.unblock_user(123456789)

        Example:
            .. code-block:: python

                user.unblock()

        Returns:
            True on success.

        Raises:
            RPCError: In case of a Telegram RPC error.
        """

        return self._client.unblock_user(self.id)

    def get_common_chats(self):
        """Bound method *get_common_chats* of :obj:`~pyrogram.types.User`.

        Use as a shortcut for:

        .. code-block:: python

            client.get_common_chats(123456789)

        Example:
            .. code-block:: python

                user.get_common_chats()

        Returns:
            True on success.

        Raises:
            RPCError: In case of a Telegram RPC error.
        """

        return self._client.get_common_chats(self.id)
