import typing_extensions as typing

from mubble.api.api import API, HTTPClient
from mubble.bot.dispatch import dispatch as dp
from mubble.bot.dispatch.abc import ABCDispatch
from mubble.bot.polling import polling as pg
from mubble.bot.polling.abc import ABCPolling
from mubble.modules import logger
from mubble.tools.loop_wrapper import ABCLoopWrapper
from mubble.tools.loop_wrapper import loop_wrapper as lw

Dispatch = typing.TypeVar(
    "Dispatch", bound=ABCDispatch, default=dp.Dispatch[HTTPClient]
)
Polling = typing.TypeVar("Polling", bound=ABCPolling, default=pg.Polling[HTTPClient])
LoopWrapper = typing.TypeVar(
    "LoopWrapper", bound=ABCLoopWrapper, default=lw.LoopWrapper
)


class Mubble(typing.Generic[HTTPClient, Dispatch, Polling, LoopWrapper]):
    def __init__(
        self,
        api: API[HTTPClient],
        *,
        dispatch: Dispatch | None = None,
        polling: Polling | None = None,
        loop_wrapper: LoopWrapper | None = None,
    ) -> None:
        self.api = api
        self.dispatch = typing.cast(Dispatch, dispatch or dp.Dispatch())
        self.polling = typing.cast(Polling, polling or pg.Polling(api))
        self.loop_wrapper = typing.cast(LoopWrapper, loop_wrapper or lw.LoopWrapper())

    def __repr__(self) -> str:
        return "<{}: api={!r}, dispatch={!r}, polling={!r}, loop_wrapper={!r}>".format(
            self.__class__.__name__,
            self.api,
            self.dispatch,
            self.polling,
            self.loop_wrapper,
        )

    @property
    def on(self) -> Dispatch:
        return self.dispatch

    async def reset_webhook(self) -> None:
        if not (await self.api.get_webhook_info()).unwrap().url:
            return
        await self.api.delete_webhook()

    async def run_polling(
        self,
        *,
        offset: int = 0,
        skip_updates: bool = False,
    ) -> typing.NoReturn:
        async def polling() -> typing.NoReturn:
            if skip_updates:
                logger.debug("Dropping pending updates")
                await self.reset_webhook()
                await self.api.delete_webhook(drop_pending_updates=True)
            self.polling.offset = offset

            async for updates in self.polling.listen():
                for update in updates:
                    logger.debug(
                        "Received update (update_id={}, update_type={!r})",
                        update.update_id,
                        update.update_type.name,
                    )
                    self.loop_wrapper.add_task(self.dispatch.feed(update, self.api))

        if self.loop_wrapper.is_running:
            await polling()
        else:
            self.loop_wrapper.add_task(polling())
            self.loop_wrapper.run_event_loop()

    def run_forever(
        self, *, offset: int = 0, skip_updates: bool = False
    ) -> typing.NoReturn:
        logger.debug("Running blocking polling (id={})", self.api.id)
        self.loop_wrapper.add_task(
            self.run_polling(offset=offset, skip_updates=skip_updates)
        )
        self.loop_wrapper.run_event_loop()


__all__ = ("Mubble",)
