"""Telegram bot frontend for Agent Johnny 5 daemon.

Uses python-telegram-bot v21.x (async). The agent brain is synchronous,
so agent calls run via asyncio.to_thread() to avoid blocking the bot.

Quick commands (/status, /restart, /health, /logs) use subprocess
directly — no LLM cost. Only freeform text messages hit OpenRouter.
"""

from __future__ import annotations

import asyncio
import logging
import subprocess
from typing import TYPE_CHECKING

from telegram import Update
from telegram.ext import (
    Application,
    CommandHandler,
    ContextTypes,
    MessageHandler,
    filters,
)

if TYPE_CHECKING:
    from ..agent.brain import AgentBrain
    from .health_monitor import HealthMonitor

from ..agent.output import OutputHandler

logger = logging.getLogger(__name__)

SERVICE_NAME = "openclaw-gateway"


class TelegramOutputHandler:
    """OutputHandler that collects agent responses for Telegram delivery."""

    def __init__(self) -> None:
        self._replies: list[str] = []

    def on_typing(self) -> None:
        pass

    def on_reply(self, text: str) -> None:
        self._replies.append(text)

    def on_tool_call(self, tool_name: str, args: dict) -> None:
        pass  # Don't spam tool calls to Telegram

    def on_error(self, error: str) -> None:
        self._replies.append(f"Error: {error}")

    def get_response(self) -> str:
        return "\n".join(self._replies)


class TelegramFrontend:
    """Telegram bot for remote Agent Johnny 5 access."""

    def __init__(
        self,
        token: str,
        allowed_user_ids: list[int],
        agent: AgentBrain,
        monitor: HealthMonitor | None = None,
    ) -> None:
        self._token = token
        self._allowed = set(allowed_user_ids)
        self._agent = agent
        self._monitor = monitor
        self._app: Application | None = None
        self._processing = False  # Prevents concurrent agent calls

    async def start(self) -> None:
        """Start the Telegram bot (long-polling). Blocks until shutdown."""
        self._app = (
            Application.builder()
            .token(self._token)
            .build()
        )

        # Quick commands (no LLM)
        self._app.add_handler(CommandHandler("start", self._cmd_start))
        self._app.add_handler(CommandHandler("status", self._cmd_status))
        self._app.add_handler(CommandHandler("restart", self._cmd_restart))
        self._app.add_handler(CommandHandler("health", self._cmd_health))
        self._app.add_handler(CommandHandler("logs", self._cmd_logs))
        self._app.add_handler(CommandHandler("help", self._cmd_help))

        # Freeform text -> agent (uses LLM)
        self._app.add_handler(
            MessageHandler(filters.TEXT & ~filters.COMMAND, self._handle_message)
        )

        logger.info("Telegram bot starting (allowed users: %s)", self._allowed)
        await self._app.initialize()
        await self._app.start()
        await self._app.updater.start_polling(drop_pending_updates=True)

        # Block until stopped
        stop_event = asyncio.Event()
        try:
            await stop_event.wait()
        except asyncio.CancelledError:
            pass
        finally:
            await self.stop()

    async def stop(self) -> None:
        """Gracefully stop the bot."""
        if self._app:
            await self._app.updater.stop()
            await self._app.stop()
            await self._app.shutdown()
            logger.info("Telegram bot stopped")

    # ------------------------------------------------------------------
    # Auth check
    # ------------------------------------------------------------------

    def _is_authorized(self, update: Update) -> bool:
        user = update.effective_user
        if not user or user.id not in self._allowed:
            return False
        return True

    async def _unauthorized(self, update: Update) -> None:
        await update.message.reply_text(
            "Not authorized. Your Telegram user ID is not in the allowed list."
        )

    # ------------------------------------------------------------------
    # Quick commands (no LLM — direct subprocess)
    # ------------------------------------------------------------------

    async def _cmd_start(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        # Save chat_id for health monitor alerts
        chat_id = update.effective_chat.id
        logger.info("Telegram /start from user %s, chat_id %s", update.effective_user.id, chat_id)

        await update.message.reply_text(
            "\U0001f916 Agent Johnny 5 is online!\n\n"
            "Quick commands:\n"
            "/status \u2014 OpenClaw service status\n"
            "/restart \u2014 Restart OpenClaw gateway\n"
            "/health \u2014 Full health check\n"
            "/logs \u2014 Recent gateway logs\n"
            "/help \u2014 Show this help\n\n"
            "Or just type a message to chat with me."
        )

    async def _cmd_help(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return
        await self._cmd_start(update, context)

    async def _cmd_status(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        try:
            result = subprocess.run(
                ["systemctl", "is-active", SERVICE_NAME],
                capture_output=True, text=True, timeout=10,
            )
            status = result.stdout.strip()
            icon = "\u2705" if status == "active" else "\u274c"
            await update.message.reply_text(f"{icon} OpenClaw gateway: {status}")
        except Exception as exc:
            await update.message.reply_text(f"Error checking status: {exc}")

    async def _cmd_restart(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        await update.message.reply_text("Restarting OpenClaw gateway...")

        try:
            subprocess.run(
                ["systemctl", "restart", SERVICE_NAME],
                capture_output=True, text=True, timeout=30,
            )
            await asyncio.sleep(8)

            result = subprocess.run(
                ["systemctl", "is-active", SERVICE_NAME],
                capture_output=True, text=True, timeout=10,
            )
            status = result.stdout.strip()
            icon = "\u2705" if status == "active" else "\u274c"
            await update.message.reply_text(
                f"{icon} Gateway restarted. Status: {status}"
            )
        except Exception as exc:
            await update.message.reply_text(f"Restart failed: {exc}")

    async def _cmd_health(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        if self._monitor:
            status = self._monitor.get_status()
            svc = "\u2705 active" if status["service_active"] else "\u274c down"
            lines = [
                f"Service: {svc}",
                f"Disk: {status['disk_percent']}%",
                f"Memory: {status['memory_percent']}%",
                f"Auto-restart: {'on' if status['auto_restart_enabled'] else 'off'}",
                f"Restarts this hour: {status['restarts_this_hour']}/{status['max_restarts_per_hour']}",
            ]
            await update.message.reply_text("\n".join(lines))
        else:
            await update.message.reply_text("Health monitor not running.")

    async def _cmd_logs(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        try:
            result = subprocess.run(
                ["journalctl", "-u", SERVICE_NAME, "-n", "30", "--no-pager"],
                capture_output=True, text=True, timeout=10,
            )
            logs = result.stdout.strip()
            if len(logs) > 4000:
                logs = logs[-4000:]  # Telegram message limit
            await update.message.reply_text(
                f"Last 30 log lines:\n\n{logs}" if logs else "No logs found."
            )
        except Exception as exc:
            await update.message.reply_text(f"Error reading logs: {exc}")

    # ------------------------------------------------------------------
    # Freeform text -> agent (uses LLM via OpenRouter)
    # ------------------------------------------------------------------

    async def _handle_message(
        self, update: Update, context: ContextTypes.DEFAULT_TYPE
    ) -> None:
        if not self._is_authorized(update):
            await self._unauthorized(update)
            return

        text = update.message.text.strip()
        if not text:
            return

        if self._processing:
            await update.message.reply_text(
                "Still processing your previous message... please wait."
            )
            return

        self._processing = True
        # Show typing indicator
        await update.effective_chat.send_action("typing")

        try:
            # Run synchronous agent in thread pool
            collector = TelegramOutputHandler()
            await asyncio.to_thread(
                self._agent.handle_user_message, text, collector
            )

            response = collector.get_response()
            if not response:
                response = "(No response from agent)"

            # Split long messages (Telegram limit: 4096 chars)
            while response:
                chunk = response[:4000]
                response = response[4000:]
                await update.message.reply_text(chunk)
        except Exception as exc:
            logger.exception("Telegram message handler error")
            await update.message.reply_text(f"Agent error: {exc}")
        finally:
            self._processing = False
