import asyncio
import base64
import datetime

from .. import types, functions
from ... import events


class QRLogin:
    """
    QR login information.

    Most of the time, you will present the `url` as a QR code to the user,
    and while it's being shown, call `wait`.
    """
    def __init__(self, client, ignored_ids):
        self._client = client
        self._request = functions.auth.ExportLoginTokenRequest(
            self._client.api_id, self._client.api_hash, ignored_ids)
        self._resp = None

    async def recreate(self):
        """
        Generates a new token and URL for a new QR code, useful if the code
        has expired before it was imported.
        """
        self._resp = await self._client(self._request)

    @property
    def token(self) -> bytes:
        """
        The binary data representing the token.

        It can be used by a previously-authorized client in a call to
        :tl:`auth.importLoginToken` to log the client that originally
        requested the QR login.
        """
        return self._resp.token

    @property
    def url(self) -> str:
        """
        The ``tg://login`` URI with the token. When opened by a Telegram
        application where the user is logged in, it will import the login
        token.

        If you want to display a QR code to the user, this is the URL that
        should be launched when the QR code is scanned (the URL that should
        be contained in the QR code image you generate).

        Whether you generate the QR code image or not is up to you, and the
        library can't do this for you due to the vast ways of generating and
        displaying the QR code that exist.

        The URL simply consists of `token` base64-encoded.
        """
        return 'tg://login?token={}'.format(base64.urlsafe_b64encode(self._resp.token).decode('utf-8').rstrip('='))

    @property
    def expires(self) -> datetime.datetime:
        """
        The `datetime` at which the QR code will expire.

        If you want to try again, you will need to call `recreate`.
        """
        return self._resp.expires

    async def wait(self, timeout: float = None):
        """
        Waits for the token to be imported by a previously-authorized client,
        either by scanning the QR, launching the URL directly, or calling the
        import method.

        This method **must** be called before the QR code is scanned, and
        must be executing while the QR code is being scanned. Otherwise, the
        login will not complete.

        Will raise `asyncio.TimeoutError` if the login doesn't complete on
        time.

        Arguments
            timeout (float):
                The timeout, in seconds, to wait before giving up. By default
                the library will wait until the token expires, which is often
                what you want.

        Returns
            On success, an instance of :tl:`User`. On failure it will raise.
        """
        if timeout is None:
            timeout = (self._resp.expires - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds()

        event = asyncio.Event()

        async def handler(_update):
            event.set()

        self._client.add_event_handler(handler, events.Raw(types.UpdateLoginToken))

        try:
            # Will raise timeout error if it doesn't complete quick enough,
            # which we want to let propagate
            await asyncio.wait_for(event.wait(), timeout=timeout)
        finally:
            self._client.remove_event_handler(handler)

        # We got here without it raising timeout error, so we can proceed
        resp = await self._client(self._request)
        if isinstance(resp, types.auth.LoginTokenMigrateTo):
            await self._client._switch_dc(resp.dc_id)
            resp = await self._client(functions.auth.ImportLoginTokenRequest(resp.token))
            # resp should now be auth.loginTokenSuccess

        if isinstance(resp, types.auth.LoginTokenSuccess):
            user = resp.authorization.user
            await self._client._on_login(user)
            return user

        raise TypeError('Login token response was unexpected: {}'.format(resp))
