from .methods import Methods
from .handlers import Handlers
from .models import Update, Chat, InlineMessage, WebHook
from .enums import UpdateEndpointTypeEnum
from typing import List, Optional
from threading import Lock
import asyncio
import httpx
import logging
import time

logging.basicConfig(
    level=logging.ERROR,
    format="%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d | %(message)s"
)

BASE_URL = "https://botapi.rubika.ir/v3/{}/"

class GetterTimer:
    def __init__(self):
        self._last = {}
        self._lock = Lock()
        self._async_lock = asyncio.Lock()

    def get(self, _id, safe=False):
        now = time.monotonic()

        with self._lock:
            last = self._last.get(_id)
            elapsed = 0 if last is None else now - last
            if not safe:
                self._last[_id] = now

        return elapsed

    async def aget(self, _id, safe=False):
        now = time.monotonic()

        async with self._async_lock:
            last = self._last.get(_id)
            elapsed = 0 if last is None else now - last
            if not safe:
                self._last[_id] = now

        return elapsed

class Client(Methods, Handlers):
    def __init__(
            self,
            token: str,
            base_url: str = None,
            webhook: WebHook = None
    ):
        self.autH_ = token
        self.BASE_URL = BASE_URL
        if base_url:
            self.BASE_URL = base_url
        
        self._run_type = webhook if webhook else 'polling'
        self.handlers = {'NewMessage': [], 'UpdatedMessage': [], 'RemovedMessage': [], "StartedBot": [], "StoppedBot": []}

    async def run_handler(
        self,
        update: Update,
        chat: Chat
    ):
        handlers = self.handlers.get(update.type, [])
        if not handlers:
            return

        if update.type == "NewMessage":
            payload = update.new_message
            payload.chat = chat
            payload.reply = self.Reply(chat.id, self.send_message)

        elif update.type == "UpdatedMessage":
            payload = update.updated_message
            payload.chat = chat
            payload.reply = self.Reply(chat.id, self.send_message)

        elif update.type == "RemovedMessage":
            payload = update.removed_message_id

        elif update.type in ("StartedBot", "StoppedBot"):
            payload = chat

        else:
            return

        tasks = [
            self._safe_call(handler, payload)
            for handler in handlers
        ]

        if tasks:
            await asyncio.gather(*tasks, return_exceptions=True)
    
    async def _safe_call(self, handler, payload):
        try:
            if hasattr(handler, "__call__"):
                await handler(payload, states=self.states)
            else:
                await handler(payload)
        except Exception:
            logging.exception("Handler crashed")
            
    async def run_updates(self):
        while True:
            update = await self._qu.get()
            try:
                chat_id = update.chat_id
                _time = await self.timer.aget(chat_id, True)

                if _time > 10 or not _time:
                    chat = await self.get_chat(chat_id)
                    chat.id = chat_id

                    async with self._lock_ch:
                        self.ch_ids[chat_id] = chat
                        await self.timer.aget(chat_id)
                else:
                    async with self._lock_ch:
                        chat = self.ch_ids.get(chat_id)

                update.chat = chat
                await self.run_handler(update, chat)

            except Exception:
                logging.exception("handler crashed")
            finally:
                self._qu.task_done()


    async def run_polling(self):
        offset_id = None

        async with httpx.AsyncClient(
            timeout=httpx.Timeout(
            connect=5,
            read=5,
            write=5,
            pool=5
        )) as client:
            
            self.client = client

            _me = await self.get_me()
            print(
                f"\n\033[33mbot is running with polling mod: "
                f"\033[36m{self.BASE_URL.replace('{}', 'YOUR-TOKEN')}\033[0m\n"
                "\033[34m-=-=-=-=-=-=- Bot status -=-=-=-=-=-=-\n"
                f"Name: {_me.bot_title}🤖\n"
                f"Username: @{_me.username}\n"
                f"Description: {_me.description}\n"
                f"Id: {_me.bot_id}\n"
                f"Access url: {_me.share_url}\n"
                f"Avatar id: {_me.avatar.file_id if _me.avatar else None}\n"
                f"Start message: {_me.start_message}\n"
                "-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-\033[0m\n"
            )

            while True:
                try:
                    updates, next_offset_id = await self.get_updates(offset_id, limit=100)
            
                    if offset_id is None:
                        offset_id = next_offset_id
                        continue

                    if offset_id == next_offset_id:
                        continue

                    if not updates:
                        await asyncio.sleep(0.005)
                        continue
                    
                    offset_id = next_offset_id
                    
                    updates = updates[:3]
                    for update in updates:
                        await self._qu.put(update)

                except httpx.ReadTimeout:
                    logging.exception("Time out")
                    continue

                except httpx.HTTPError as e:
                    logging.warning(f"http error: {e}")
                    await asyncio.sleep(0.2)
                
                except asyncio.CancelledError:
                    raise
                except Exception:
                    logging.exception("Unhandled polling exception")
                    await asyncio.sleep(2)

    """async def webhook_init(self):
        async with httpx.AsyncClient(
            timeout=httpx.Timeout(
            connect=5,
            read=5,
            write=5,
            pool=5
        )) as client:
            
            self.client = client
            

            _me = await self.get_me()
            print(
                f"\n\033[33mbot is running with webhook mod: "
                f"\033[36m{self._run_type}\033[0m\n"
                "\033[34m-=-=-=-=-=-=- Bot status -=-=-=-=-=-=-\n"
                f"Name: {_me.bot_title}🤖\n"
                f"Username: @{_me.username}\n"
                f"Description: {_me.description}\n"
                f"Id: {_me.bot_id}\n"
                f"Access url: {_me.share_url}\n"
                f"Avatar id: {_me.avatar.file_id if _me.avatar else None}\n"
                f"Start message: {_me.start_message}\n"
                "-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-\033[0m\n"
            )

            app = FastAPI()

            @app.post('/receiveUpdate')
            async def receiveUpdate(request: Request):
                try:
                    res = await request.json()
                    update = Update()
                    update._builder(res['update'])
                    await self._qu.put(update)
                except:
                    logging.exception('Unhandled webhook exception:')

                return {'status': 'ok'}
            
            @app.post('/receiveInlineMessage')
            async def receiveInlineMessage(request: Request):
                try:
                    res = await request.json()
                    inline_message = InlineMessage()
                    inline_message._builder(res['inline_message'])
                    inline_message.type = 'InlineQuery'
                    await self._qu.put(inline_message)
                except:
                    logging.exception('Unhandled webhook exception:')

                return {'status': 'ok'}

            uvicorn.run(app, host="0.0.0.0", port=self.webhook_port)"""

    def run(self):
        self.ch_ids = dict()
        self.timer = GetterTimer()
        asyncio.run(self.init())

    async def init(self):
        self._lock_ch = asyncio.Lock()
        self._qu = asyncio.Queue(maxsize=1000)
        for _ in range(8):
            asyncio.create_task(self.run_updates())
        self.states = {}
        self.states_lock = asyncio.Lock()

        if self._run_type == 'polling':
            await self.run_polling()
            return
        
        await self.webhook_init()