import requests
from typing import List, Optional, Dict, Any, Literal
from .exceptions import APIRequestError
from .logger import logger
from typing import Callable
from .context import Message,InlineMessage
API_URL = "https://botapi.rubika.ir/v3"
import sys
import subprocess
import requests
def install_package(package_name):
    try:
        subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return True
    except Exception:return False

def get_importlib_metadata():
    try:
        from importlib.metadata import version, PackageNotFoundError
        return version, PackageNotFoundError
    except ImportError:
        if install_package("importlib-metadata"):
            try:
                from importlib_metadata import version, PackageNotFoundError
                return version, PackageNotFoundError
            except ImportError:
                return None, None
        return None, None

version, PackageNotFoundError = get_importlib_metadata()
def get_installed_version(package_name: str) -> str:
    if version is None:return "unknown"
    try:
        return version(package_name)
    except PackageNotFoundError:
        return None
def get_latest_version(package_name: str) -> str:
    url = f"https://pypi.org/pypi/{package_name}/json"
    try:
        resp = requests.get(url, timeout=5)
        resp.raise_for_status()
        data = resp.json()
        return data["info"]["version"]
    except Exception:return None
def check_rubka_version():
    package_name = "rubka"
    installed_version = get_installed_version(package_name)
    if installed_version is None:return
    latest_version = get_latest_version(package_name)
    if latest_version is None:return
    if installed_version != latest_version:
        print(f"\n\n⚠️ WARNING: Your installed version of '{package_name}' is outdated!")
        print(f"Installed version: {installed_version}")
        print(f"Latest version:    {latest_version}")
        print(f"Please update it using:\n\npip install --upgrade {package_name}\n")

check_rubka_version()
class Robot:
    """
    Main class to interact with Rubika Bot API.
    Initialized with bot token.
    """

    def __init__(self, token: str):
        self.token = token
        self._offset_id = None
        self.session = requests.Session()
        self.sessions: Dict[str, Dict[str, Any]] = {}
        self._callback_handler = None
        self._message_handler = None
        self._inline_query_handler = None


        logger.info(f"Initialized RubikaBot with token: {token[:8]}***")

    def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
        url = f"{API_URL}/{self.token}/{method}"
        try:
            response = self.session.post(url, json=data, timeout=10)
            response.raise_for_status()
            try:
                json_resp = response.json()
            except ValueError:
                logger.error(f"Invalid JSON response from {method}: {response.text}")
                raise APIRequestError(f"Invalid JSON response: {response.text}")
            if method != "getUpdates":logger.debug(f"API Response from {method}: {json_resp}")
            
            return json_resp
        except requests.RequestException as e:
            logger.error(f"API request failed: {e}")
            raise APIRequestError(f"API request failed: {e}") from e


    def get_me(self) -> Dict[str, Any]:
        """Get info about the bot itself."""
        return self._post("getMe", {})
    def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
        def decorator(func: Callable[[Any, Message], None]):
            self._message_handler = {
                "func": func,
                "filters": filters,
                "commands": commands
            }
            return func
        return decorator


    def on_inline_query(self):
        def decorator(func: Callable[[Any, InlineMessage], None]):
            self._inline_query_handler = func
            return func
        return decorator



    def _process_update(self, update: Dict[str, Any]):
        if update.get('type') == 'ReceiveQuery':
            msg = update.get("inline_message", {})
            if self._inline_query_handler:
                context = InlineMessage(bot=self, raw_data=msg)
                self._inline_query_handler(self, context)
        if update.get('type') == 'NewMessage':
            msg = update.get('new_message', {})
            chat_id = update.get('chat_id')
            message_id = msg.get('message_id')
            sender_id = msg.get('sender_id')
            text = msg.get('text')

            if self._message_handler:
                handler = self._message_handler
                context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)

                if handler["commands"]:
                    if not context.text or not context.text.startswith("/"):
                        return
                    parts = context.text.split()
                    cmd = parts[0][1:]
                    if cmd not in handler["commands"]:
                        return
                    context.args = parts[1:]

                if handler["filters"]:
                    if not handler["filters"](context):
                        return

                handler["func"](self, context)
        elif update.get('type') == 'ReceiveQuery':
            msg = update.get("inline_message", {})
            chat_id = msg.get("chat_id")
            message_id = msg.get("message_id")
            sender_id = msg.get("sender_id")
            text = msg.get("text")

            if hasattr(self, "_callback_handler"):
                context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
                self._callback_handler(self, context)

    def run(self):
        print("Bot started running...")
        if self._offset_id is None:
            try:
                latest = self.get_updates(limit=100)
                if latest and latest.get("data") and latest["data"].get("updates"):
                    updates = latest["data"]["updates"]
                    last_update = updates[-1]
                    self._offset_id = latest["data"].get("next_offset_id")
                    print(f"Offset initialized to: {self._offset_id}")
                else:
                    print("No updates found.")
            except Exception as e:
                print(f"Failed to fetch latest message: {e}")

        while True:
            try:
                updates = self.get_updates(offset_id=self._offset_id, limit=1)
                if updates and updates.get("data"):
                    for update in updates["data"].get("updates", []):
                        self._process_update(update)
                    self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
            except Exception as e:
                print(f"Error in run loop: {e}")

    def send_message(
        self,
        chat_id: str,
        text: str,
        chat_keypad: Optional[Dict[str, Any]] = None,
        inline_keypad: Optional[Dict[str, Any]] = None,
        disable_notification: bool = False,
        reply_to_message_id: Optional[str] = None,
        chat_keypad_type: Optional[Literal["New", "Removed"]] = None
    ) -> Dict[str, Any]:
        """
        Send a text message to a chat.
        """
        payload = {
            "chat_id": chat_id,
            "text": text,
            "disable_notification": disable_notification
        }
        if chat_keypad:
            payload["chat_keypad"] = chat_keypad
        if inline_keypad:
            payload["inline_keypad"] = inline_keypad
        if reply_to_message_id:
            payload["reply_to_message_id"] = reply_to_message_id
        if chat_keypad_type:
            payload["chat_keypad_type"] = chat_keypad_type

        return self._post("sendMessage", payload)

    def send_poll(
        self,
        chat_id: str,
        question: str,
        options: List[str]
    ) -> Dict[str, Any]:
        """
        Send a poll to a chat.
        """
        return self._post("sendPoll", {
            "chat_id": chat_id,
            "question": question,
            "options": options
        })

    def send_location(
        self,
        chat_id: str,
        latitude: str,
        longitude: str,
        disable_notification: bool = False,
        inline_keypad: Optional[Dict[str, Any]] = None,
        reply_to_message_id: Optional[str] = None,
        chat_keypad_type: Optional[Literal["New", "Removed"]] = None
    ) -> Dict[str, Any]:
        """
        Send a location to a chat.
        """
        payload = {
            "chat_id": chat_id,
            "latitude": latitude,
            "longitude": longitude,
            "disable_notification": disable_notification,
            "inline_keypad": inline_keypad,
            "reply_to_message_id": reply_to_message_id,
            "chat_keypad_type": chat_keypad_type
        }
        # Remove None values
        payload = {k: v for k, v in payload.items() if v is not None}
        return self._post("sendLocation", payload)

    def send_contact(
        self,
        chat_id: str,
        first_name: str,
        last_name: str,
        phone_number: str
    ) -> Dict[str, Any]:
        """
        Send a contact to a chat.
        """
        return self._post("sendContact", {
            "chat_id": chat_id,
            "first_name": first_name,
            "last_name": last_name,
            "phone_number": phone_number
        })

    def get_chat(self, chat_id: str) -> Dict[str, Any]:
        """Get chat info."""
        return self._post("getChat", {"chat_id": chat_id})

    def get_updates(
        self,
        offset_id: Optional[str] = None,
        limit: Optional[int] = None
    ) -> Dict[str, Any]:
        """Get updates."""
        data = {}
        if offset_id:
            data["offset_id"] = offset_id
        if limit:
            data["limit"] = limit
        return self._post("getUpdates", data)

    def forward_message(
        self,
        from_chat_id: str,
        message_id: str,
        to_chat_id: str,
        disable_notification: bool = False
    ) -> Dict[str, Any]:
        """Forward a message from one chat to another."""
        return self._post("forwardMessage", {
            "from_chat_id": from_chat_id,
            "message_id": message_id,
            "to_chat_id": to_chat_id,
            "disable_notification": disable_notification
        })

    def edit_message_text(
        self,
        chat_id: str,
        message_id: str,
        text: str
    ) -> Dict[str, Any]:
        """Edit text of an existing message."""
        return self._post("editMessageText", {
            "chat_id": chat_id,
            "message_id": message_id,
            "text": text
        })

    def edit_inline_keypad(
        self,
        chat_id: str,
        message_id: str,
        inline_keypad: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Edit inline keypad of a message."""
        return self._post("editInlineKeypad", {
            "chat_id": chat_id,
            "message_id": message_id,
            "inline_keypad": inline_keypad
        })

    def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
        """Delete a message from chat."""
        return self._post("deleteMessage", {
            "chat_id": chat_id,
            "message_id": message_id
        })

    def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
        """Set bot commands."""
        return self._post("setCommands", {"bot_commands": bot_commands})

    def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
        """Update bot endpoint (Webhook or Polling)."""
        return self._post("updateBotEndpoints", {
            "url": url,
            "type": type
        })

    def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
        """Remove chat keypad."""
        return self._post("editChatKeypad", {
            "chat_id": chat_id,
            "chat_keypad_type": "Removed"
        })

    def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
        """Edit or add new chat keypad."""
        return self._post("editChatKeypad", {
            "chat_id": chat_id,
            "chat_keypad_type": "New",
            "chat_keypad": chat_keypad
        })
