import logging
import warnings
import asyncio
import time
import inspect
import os

from aiohttp import ClientSession, ClientTimeout, TCPConnector
from concurrent.futures import ThreadPoolExecutor
from collections import deque
from rubxy.methods import Methods
from rubxy.handlers import Handler
from rubxy.dispatcher import Dispatcher
from typing import Any, Union, Literal, Dict, List, Callable, Optional

from . import Plugins
from ._config import (
    DEAFULT_HOST,
    DEAFULT_PORT,
    DEAFULT_PATH,
    DEAFULT_TIMEOUT,
    DEAFULT_RATE_LIMIT,
    DEAFULT_POLL_INTERVAL
)

logger = logging.getLogger(__name__)

class Client(Methods):
    """
    Rubika Client, the main means for interacting with Rubika.

    ---

    **Parameters:**

    - `bot_token` (`str`): Pass the Bot API token to create a bot.
    - `plugins` (`dict`, optional): Smart Plugins settings as dict, e.g. `dict(root="plugins")`.
    - `timeout` (`float`, optional): Long polling wait time in seconds (default: 30).
    """

    def __init__(
        self,
        bot_token: str,
        plugins: Optional[Plugins] = None,
        timeout: Optional[float] = DEAFULT_TIMEOUT,
        rate_limit: Optional[float] = DEAFULT_RATE_LIMIT,
        poll_interval: Optional[float] = DEAFULT_POLL_INTERVAL,
        executor: Optional[ThreadPoolExecutor] = None,
        loop: Optional[asyncio.BaseEventLoop] = None
    ):
        self.bot_token: str = bot_token
        self.base_url: str = "https://botapi.rubika.ir/v3/{}/".format(bot_token)
        self.http: ClientSession = None
        self.connector: TCPConnector = None
        self.is_started: bool = False
        self.is_long_polling: bool = None
        self.middlewares: List[Callable] = []
        self.dispatcher: Dispatcher = Dispatcher(self, poll_interval=poll_interval)
        self.plugins: Plugins = plugins
        self.timeout: int = timeout
        self.completed_updates = deque(maxlen=200)
        self.rate_limit: float = rate_limit
        self.last_request = 0
        self.executor = executor or ThreadPoolExecutor(min(32, (os.cpu_count() or 1) + 4))
        self.loop = loop

        self.start_handlers: List[Callable] = []
        self.stop_handlers: List[Callable] = []
        
        if not self.loop:
            try:
                self.loop = asyncio.get_running_loop()
            except RuntimeError:
                self.loop = asyncio.new_event_loop()
                asyncio.set_event_loop(self.loop)
        
    async def start(self):
        if not self.is_started:
            self.is_started = True
            timeout = ClientTimeout(total=self.timeout)
            
            if self.connector is None:
                self.connector = TCPConnector(limit=50)
            
            if self.http is None:
                self.http = ClientSession(
                    base_url=self.base_url,
                    connector=self.connector,
                    timeout=timeout,
                )
        
        for handler in self.start_handlers:
            if inspect.iscoroutinefunction(handler):
                await handler(self)
            else:
                self.loop.run_in_executor(
                    self.executor,
                    handler,
                    self
                )
        
        return self
    
    async def stop(self):
        if self.is_started:
            self.is_started = False
            self.dispatcher.is_running = False

            for handler in self.stop_handlers:
                if inspect.iscoroutinefunction(handler):
                    await handler(self)
                else:
                    self.loop.run_in_executor(
                        self.executor,
                        handler,
                        self
                    )

    def __enter__(self):
        self.start()
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        self.stop()

    async def __aenter__(self):
        await self.start()
        return self
    
    async def __aexit__(self, exc_type, exc_value, traceback):
        await self.stop()

    async def _rate_limit(self):
        passed = time.time() - self.last_request

        if passed < self.rate_limit:
            await asyncio.sleep(self.rate_limit - passed)
        
        self.last_request = time.time()

    def load_plugins(self):
        plugins = self.plugins

        if plugins.root:
            pass

    def run(
        self,
        endpoint: Optional[str] = None,
        host: Optional[str] = DEAFULT_HOST,
        path: Optional[str] = DEAFULT_PATH,
        port: Optional[int] = DEAFULT_PORT,
        update_endpoints: Optional[bool] = True
    ) -> None:
        runner = self.loop.run_until_complete
        runner(self.start())

        if endpoint:
            if endpoint.startswith('http'):
                runner(self.dispatcher.setup_webhook(host=host, path=path, port=port, endpoint=endpoint, update_endpoints=update_endpoints))

                try:
                    self.loop.run_forever()
                except KeyboardInterrupt as e:
                    runner(self.stop())
                    raise e
                finally:
                    runner(self.dispatcher.app.cleanup())
            else:
                raise ValueError("Endpoint format is incorrect, it should be starts with http")
        else:
            warnings.warn(
                "Note: long-polling cannot receive `InlineMessage` updates",
                UserWarning
            )

            self.is_long_polling = True

            try:
                runner(self.dispatcher.updater(limit=100))
            except KeyboardInterrupt as e:
                runner(self.stop())
                raise e