"""BotManager — manages Discord gateway instances for all enabled configs.

In peon-grukk, the BotManager starts bots from config dicts fetched via the
REST API (no direct DB access).  The ``start_from_configs()`` method is the
primary entry point.
"""

from __future__ import annotations

import logging
from typing import Any

from peon_mcp.grukk.discord.gateway import DiscordGateway

logger = logging.getLogger(__name__)


class BotManager:
    """Manages the lifecycle of multiple :class:`DiscordGateway` instances.

    Each active Discord config gets one ``DiscordGateway`` stored in
    ``self._gateways`` keyed by ``config_id``.
    """

    def __init__(self) -> None:
        self._gateways: dict[int, DiscordGateway] = {}

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def start_all_enabled(self, db: Any) -> None:
        """Start gateways for every enabled discord_config in the database.

        Called from the FastAPI lifespan on application startup.

        Args:
            db: The database connection.
        """
        try:
            rows = await db.execute_fetchall(
                "SELECT * FROM discord_configs WHERE enabled = TRUE"
            )
        except Exception as exc:
            logger.warning("Could not load discord configs on startup: %s", exc)
            return

        for row in rows:
            config = dict(row)
            config_id = config["id"]
            token = config.get("bot_token", "")
            if not token:
                logger.debug("Discord config %s has no token — skipping.", config_id)
                continue
            try:
                await self.start_bot(config_id, token, config)
            except Exception as exc:
                logger.error("Failed to start Discord bot %s: %s", config_id, exc)

    async def start_from_configs(self, configs: list[dict[str, Any]]) -> None:
        """Start bots from config dicts (fetched via REST API).

        This is the primary entry point for peon-grukk, which has no direct
        DB access and instead fetches configs from the REST API.

        Args:
            configs: List of discord_config dicts from ``GET /api/discord/configs``.
        """
        for config in configs:
            if not config.get("enabled"):
                continue
            config_id = config.get("id")
            token = config.get("bot_token", "")
            if not token:
                logger.debug("Discord config %s has no token — skipping.", config_id)
                continue
            try:
                await self.start_bot(config_id, token, config)
            except Exception as exc:
                logger.error("Failed to start Discord bot %s: %s", config_id, exc)

    async def stop_all(self) -> None:
        """Stop all running gateways."""
        config_ids = list(self._gateways.keys())
        for config_id in config_ids:
            try:
                await self.stop_bot(config_id)
            except Exception as exc:
                logger.error("Error stopping Discord bot %s: %s", config_id, exc)

    # ------------------------------------------------------------------
    # Per-bot controls
    # ------------------------------------------------------------------

    async def start_bot(
        self,
        config_id: int,
        token: str,
        config: dict[str, Any],
    ) -> None:
        """Start (or restart) the gateway for *config_id*.

        If a gateway is already running it is stopped first.

        Args:
            config_id: Discord config ID.
            token: Discord bot token.
            config: The discord_config row as a dict.
        """
        existing = self._gateways.get(config_id)
        if existing and existing.is_connected:
            logger.debug("Bot %s already connected — skipping start.", config_id)
            return

        if existing:
            await existing.stop()

        gateway = DiscordGateway(token=token, config=config)
        self._gateways[config_id] = gateway
        await gateway.start()
        logger.info("Started Discord bot for config_id=%s", config_id)

    async def stop_bot(self, config_id: int) -> None:
        """Stop the gateway for *config_id* if it is running."""
        gateway = self._gateways.pop(config_id, None)
        if gateway is None:
            return
        await gateway.stop()
        logger.info("Stopped Discord bot for config_id=%s", config_id)

    async def restart_bot(
        self,
        config_id: int,
        token: str,
        config: dict[str, Any],
    ) -> None:
        """Stop then start the gateway for *config_id*."""
        await self.stop_bot(config_id)
        await self.start_bot(config_id, token, config)

    async def send_message(
        self,
        config_id: int,
        channel_id: str,
        content: str = "",
        embed_title: str = "",
        embed_description: str = "",
    ) -> bool:
        """Send a message via the gateway for *config_id*.

        Returns True if sent successfully, False if the bot is not connected
        or the send fails.
        """
        gateway = self._gateways.get(config_id)
        if gateway is None:
            logger.warning("No gateway for config_id=%s — cannot send message.", config_id)
            return False
        return await gateway.send_message(
            channel_id=channel_id,
            content=content,
            embed_title=embed_title,
            embed_description=embed_description,
        )

    # ------------------------------------------------------------------
    # Status
    # ------------------------------------------------------------------

    def get_bot_status(self, config_id: int) -> str:
        """Return the connection status string for *config_id*.

        Returns one of: ``"connected"``, ``"disconnected"``, ``"error"``.
        """
        gateway = self._gateways.get(config_id)
        if gateway is None:
            return "disconnected"
        if gateway._error:
            return "error"
        if gateway.is_connected:
            return "connected"
        # Gateway object exists but client is not yet ready (e.g. connecting)
        return "disconnected"

    def get_bot_error(self, config_id: int) -> str | None:
        """Return the last error message for *config_id*, or None."""
        gateway = self._gateways.get(config_id)
        return gateway._error if gateway else None


# Module-level singleton — initialised once per process.
bot_manager = BotManager()
