"""
ONEMIX Bot SDK — Core Bot class
"""

import re
import time
import json
import logging
import threading
from typing import Optional, List, Dict, Callable, Any

import requests

logger = logging.getLogger("onemix_bot")


class OnemixBot:
    """
    Main class for interacting with the ONEMIX Bot API.

    Args:
        token: Bot API token (from BotFather)
        api_url: Base URL of the ONEMIX server (e.g., "https://your-server.com")
        poll_interval: Seconds between polling requests (default: 2)
    """

    def __init__(self, token: str, api_url: str = "", poll_interval: float = 2.0):
        self.token = token
        self.api_url = api_url.rstrip("/")
        self.poll_interval = poll_interval

        self._command_handlers: Dict[str, Callable] = {}
        self._message_handlers: List[Dict[str, Any]] = []
        self._running = False
        self._seen_ids: set = set()
        self._session = requests.Session()
        self._session.headers.update({
            "Authorization": f"Bot {self.token}",
            "Content-Type": "application/json",
        })

    # ── API Methods ──────────────────────────────────────────────

    def _request(self, method: str, path: str, **kwargs) -> Any:
        """Make an HTTP request to the ONEMIX Bot API."""
        url = f"{self.api_url}{path}"
        try:
            resp = self._session.request(method, url, **kwargs)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.HTTPError as e:
            logger.error("API error %s %s: %s — %s", method, path, e, resp.text)
            raise
        except requests.exceptions.RequestException as e:
            logger.error("Request failed %s %s: %s", method, path, e)
            raise

    def get_me(self) -> dict:
        """Get information about this bot."""
        data = self._request("GET", "/bot-api/getMe")
        return data.get("result", data)

    def send_message(self, chat_id: str, text: str) -> dict:
        """
        Send a text message to a bot chat.

        Args:
            chat_id: The bot_chat_id to send to
            text: Message text

        Returns:
            dict with ok and message_id
        """
        return self._request("POST", "/bot-api/sendMessage", json={
            "chat_id": chat_id,
            "text": text,
        })

    def reply(self, message: dict, text: str) -> dict:
        """
        Convenience method to reply to a received message.

        Args:
            message: The incoming message dict (must have bot_chat_id)
            text: Reply text

        Returns:
            dict with ok and message_id
        """
        chat_id = message.get("bot_chat_id")
        if not chat_id:
            raise ValueError("Message has no bot_chat_id")
        return self.send_message(chat_id, text)

    def get_updates(self, limit: int = 100) -> List[dict]:
        """
        Get recent messages sent to this bot.

        Args:
            limit: Maximum number of updates to fetch

        Returns:
            List of message dicts
        """
        data = self._request("GET", f"/bot-api/getUpdates?limit={limit}")
        return data.get("result", [])

    def set_webhook(self, url: str, secret: Optional[str] = None) -> dict:
        """
        Set a webhook URL for receiving updates.

        Args:
            url: Your webhook endpoint (must be HTTPS in production)
            secret: Optional secret for verifying webhook calls
        """
        me = self.get_me()
        bot_id = me.get("id")
        if not bot_id:
            raise ValueError("Could not get bot ID")
        payload = {"url": url}
        if secret:
            payload["secret"] = secret
        return self._request("POST", f"/botfather/{bot_id}/webhook", json=payload)

    def delete_webhook(self) -> dict:
        """Remove the webhook."""
        me = self.get_me()
        bot_id = me.get("id")
        if not bot_id:
            raise ValueError("Could not get bot ID")
        return self._request("DELETE", f"/botfather/{bot_id}/webhook")

    def set_commands(self, commands: List[Dict[str, str]]) -> dict:
        """
        Set bot commands visible to users.

        Args:
            commands: List of {"command": "...", "description": "..."}
        """
        me = self.get_me()
        bot_id = me.get("id")
        if not bot_id:
            raise ValueError("Could not get bot ID")
        return self._request("POST", f"/botfather/{bot_id}/commands", json={
            "commands": commands,
        })

    # ── Decorators ───────────────────────────────────────────────

    def command(self, cmd: str):
        """
        Decorator to register a command handler.

        Usage:
            @bot.command("start")
            def on_start(msg):
                bot.reply(msg, "Hello!")
        """
        def decorator(func: Callable):
            clean_cmd = cmd.lstrip("/").lower()
            self._command_handlers[clean_cmd] = func
            return func
        return decorator

    def on_message(self, pattern: Optional[str] = None):
        """
        Decorator to register a message handler.

        Args:
            pattern: Optional regex pattern to match against message content

        Usage:
            @bot.on_message()
            def on_text(msg):
                bot.reply(msg, "Got it!")

            @bot.on_message(pattern=r"hello|hi")
            def on_hello(msg):
                bot.reply(msg, "Hey!")
        """
        def decorator(func: Callable):
            compiled = re.compile(pattern, re.IGNORECASE) if pattern else None
            self._message_handlers.append({
                "func": func,
                "pattern": compiled,
            })
            return func
        return decorator

    # ── Update Processing ────────────────────────────────────────

    def process_update(self, update: dict):
        """
        Process a single update (message). Used for webhook mode.

        Args:
            update: The update dict from the webhook POST body
        """
        msg = update.get("message", update)
        content = msg.get("content", "")

        # Try command handlers first
        if content.startswith("/"):
            parts = content.split(None, 1)
            cmd_name = parts[0].lstrip("/").lower()
            if cmd_name in self._command_handlers:
                try:
                    self._command_handlers[cmd_name](msg)
                except Exception as e:
                    logger.error("Command handler error /%s: %s", cmd_name, e)
                return

        # Try message handlers
        for handler in self._message_handlers:
            if handler["pattern"]:
                if handler["pattern"].search(content):
                    try:
                        handler["func"](msg)
                    except Exception as e:
                        logger.error("Message handler error: %s", e)
                    return
            else:
                try:
                    handler["func"](msg)
                except Exception as e:
                    logger.error("Message handler error: %s", e)
                return

    def _poll_once(self):
        """Fetch and process new updates once."""
        try:
            updates = self.get_updates(limit=100)
            for update in updates:
                msg_id = update.get("id")
                if msg_id and msg_id not in self._seen_ids:
                    self._seen_ids.add(msg_id)
                    self.process_update(update)

            # Keep seen_ids from growing infinitely
            if len(self._seen_ids) > 10000:
                self._seen_ids = set(list(self._seen_ids)[-5000:])

        except Exception as e:
            logger.error("Poll error: %s", e)

    # ── Main Loop ────────────────────────────────────────────────

    def run(self, api_url: Optional[str] = None):
        """
        Start the long-polling loop. Blocks the current thread.

        Args:
            api_url: Override the API URL (optional)
        """
        if api_url:
            self.api_url = api_url.rstrip("/")

        if not self.api_url:
            raise ValueError("api_url is required. Pass it to OnemixBot() or run().")

        self._running = True
        me = self.get_me()
        bot_name = me.get("name", "Unknown")
        bot_username = me.get("username", "unknown")
        logger.info("Bot started: %s (@%s)", bot_name, bot_username)
        print(f"[onemix-bot] Started: {bot_name} (@{bot_username})")
        print(f"[onemix-bot] Polling {self.api_url} every {self.poll_interval}s")
        print(f"[onemix-bot] Registered: {len(self._command_handlers)} commands, "
              f"{len(self._message_handlers)} message handlers")

        # Pre-fill seen IDs so we don't process old messages
        try:
            old = self.get_updates(limit=100)
            for u in old:
                mid = u.get("id")
                if mid:
                    self._seen_ids.add(mid)
            logger.info("Skipped %d existing messages", len(self._seen_ids))
        except Exception:
            pass

        try:
            while self._running:
                self._poll_once()
                time.sleep(self.poll_interval)
        except KeyboardInterrupt:
            print("\n[onemix-bot] Stopped")
            self._running = False

    def stop(self):
        """Stop the polling loop."""
        self._running = False

    def run_threaded(self, api_url: Optional[str] = None) -> threading.Thread:
        """
        Start the polling loop in a background thread.

        Returns:
            The started Thread object
        """
        t = threading.Thread(target=self.run, args=(api_url,), daemon=True)
        t.start()
        return t
