# extergram/core.py

import requests
import json
import time
import asyncio
import inspect
from .ui import ButtonsDesign
from .api_types import Update, Message, CallbackQuery
from .ext.base import BaseHandler
from .errors import APIError

class Bot:
    """
    Основной класс для создания Telegram-бота и взаимодействия с API.
    """
    def __init__(self, token: str):
        self.token = token
        self.api_url = f"https://api.telegram.org/bot{self.token}/"
        self.handlers = []
        self._offset = None

    def _make_request(self, method: str, params: dict = None):
        try:
            response = requests.post(self.api_url + method, json=params)
            response.raise_for_status()
            data = response.json()
            if not data.get('ok'):
                raise APIError(data.get('description', 'Unknown error'), data.get('error_code', -1))
            return data
        except requests.exceptions.RequestException as e:
            raise APIError(f"Network error: {e}", -1)
        except json.JSONDecodeError:
            raise APIError("Failed to decode JSON response", -1)

    def add_handler(self, handler: BaseHandler):
        """
        Регистрирует новый обработчик событий.
        """
        if not isinstance(handler, BaseHandler):
            raise TypeError("handler must be an instance of BaseHandler")
        self.handlers.append(handler)

    async def _process_update(self, update: Update):
        """Асинхронно обрабатывает одно обновление."""
        event = update.message or update.callback_query
        if not event:
            return

        tasks = []
        for handler in self.handlers:
            if handler.check_update(update):
                # Создаем задачу для каждого подходящего обработчика
                if inspect.iscoroutinefunction(handler.callback):
                    tasks.append(asyncio.create_task(handler.callback(self, event)))
                else:
                    # Для синхронных функций можно запустить в исполнителе,
                    # чтобы не блокировать основной поток.
                    loop = asyncio.get_running_loop()
                    tasks.append(loop.run_in_executor(None, handler.callback, self, event))
        
        if tasks:
            await asyncio.gather(*tasks)


    async def _polling_loop(self, timeout: int = 30):
        """Основной асинхронный цикл опроса."""
        while True:
            try:
                updates_data = self.get_updates(offset=self._offset, timeout=timeout)
                updates = updates_data.get('result', [])
                if updates:
                    for raw_update in updates:
                        self._offset = raw_update['update_id'] + 1
                        update_obj = Update(raw_update)
                        # Запускаем обработку каждого обновления как отдельную задачу
                        asyncio.create_task(self._process_update(update_obj))
            except APIError as e:
                print(f"API Error: {e}")
                await asyncio.sleep(5)
            except Exception as e:
                print(f"An unexpected error occurred: {e}")
                await asyncio.sleep(10)

    def polling(self, timeout: int = 30):
        """Запускает бота в режиме длинного опроса."""
        print("Bot started polling...")
        try:
            asyncio.run(self._polling_loop(timeout))
        except KeyboardInterrupt:
            print("Bot stopped.")

    # --- Методы API ---
    def get_updates(self, offset: int = None, timeout: int = 30):
        params = {'timeout': timeout, 'offset': offset}
        return self._make_request('getUpdates', params)

    def send_message(self, chat_id: int, text: str, parse_mode: str = None, reply_markup: dict = None):
        params = {'chat_id': chat_id, 'text': text}
        if parse_mode:
            params['parse_mode'] = parse_mode
        if isinstance(reply_markup, ButtonsDesign):
            params['reply_markup'] = reply_markup.to_dict()
        elif reply_markup:
            params['reply_markup'] = reply_markup
        return self._make_request('sendMessage', params)

    def edit_message_text(self, chat_id: int, message_id: int, text: str, parse_mode: str = None, reply_markup: dict = None):
        params = {'chat_id': chat_id, 'message_id': message_id, 'text': text}
        if parse_mode:
            params['parse_mode'] = parse_mode
        if isinstance(reply_markup, ButtonsDesign):
            params['reply_markup'] = reply_markup.to_dict()
        elif reply_markup:
            params['reply_markup'] = reply_markup
        return self._make_request('editMessageText', params)
    
    def answer_callback_query(self, callback_query_id: str, text: str = None, show_alert: bool = False):
        params = {'callback_query_id': callback_query_id}
        if text:
            params['text'] = text
        params['show_alert'] = show_alert
        return self._make_request('answerCallbackQuery', params)
        
    def delete_message(self, chat_id: int, message_id: int):
        params = {'chat_id': chat_id, 'message_id': message_id}
        return self._make_request('deleteMessage', params)