# 中文注释：
# 文件：echobot/channels/manager.py
# 说明：渠道适配层，负责 Telegram/WhatsApp 等消息收发。

"""Channel manager for coordinating chat channels."""

import asyncio
from typing import Any

from loguru import logger

from echobot.bus.queue import MessageBus
from echobot.channels.base import BaseChannel
from echobot.config.schema import Config


class ChannelManager:
    """
    Manages chat channels and coordinates message routing.

    Responsibilities:
    - Initialize enabled channels (Telegram, etc.)
    - Start/stop channels
    - Route outbound messages
    """

    def __init__(self, config: Config, bus: MessageBus):
        self.config = config
        self.bus = bus
        self.channels: dict[str, BaseChannel] = {}
        self._dispatch_task: asyncio.Task | None = None
        # 记录每个渠道对应的后台任务，便于后续优雅停止和异常追踪。
        self._channel_tasks: dict[str, asyncio.Task] = {}

        self._init_channels()

    def _init_channels(self) -> None:
        """Initialize channels based on config."""

        # Telegram channel
        if self.config.channels.telegram.enabled:
            try:
                from echobot.channels.telegram import TelegramChannel

                self.channels["telegram"] = TelegramChannel(self.config.channels.telegram, self.bus)
                logger.info("Telegram channel enabled")
            except ImportError as e:
                logger.warning(f"Telegram channel not available: {e}")

    def _attach_channel_task_monitor(self, channel_name: str, task: asyncio.Task) -> None:
        """
        为渠道后台任务附加监控回调。

        目的：
        1. 渠道协程异常退出时，立即记录错误而不是静默失效。
        2. 任务被主动取消时仅记录调试信息，避免误报。
        """

        def _on_done(done_task: asyncio.Task) -> None:
            try:
                done_task.result()
                logger.info(f"{channel_name} channel task exited normally")
            except asyncio.CancelledError:
                logger.debug(f"{channel_name} channel task cancelled")
            except Exception as e:
                logger.error(f"{channel_name} channel task crashed: {e}")

        task.add_done_callback(_on_done)

    async def start_all(self) -> None:
        """
        启动所有渠道与出站分发器（非阻塞）。

        设计要点：
        1. 仅创建并保存后台任务，不在这里 `await` 渠道主循环。
        2. 这样调用方（Gateway）可以继续启动 Agent 主循环，避免被渠道启动流程阻塞。
        """
        if not self.channels:
            logger.warning("No channels enabled")
            return

        # 启动出站分发器（只启动一次）。
        if not self._dispatch_task or self._dispatch_task.done():
            self._dispatch_task = asyncio.create_task(self._dispatch_outbound())

        # 启动所有渠道（每个渠道一个后台任务），并保存任务句柄。
        for name, channel in self.channels.items():
            # 如果任务仍在运行则跳过，避免重复启动同一渠道。
            existing = self._channel_tasks.get(name)
            if existing and not existing.done():
                logger.debug(f"{name} channel is already running, skip duplicate start")
                continue

            logger.info(f"Starting {name} channel...")
            task = asyncio.create_task(channel.start())
            self._attach_channel_task_monitor(name, task)
            self._channel_tasks[name] = task

    async def stop_all(self) -> None:
        """Stop all channels and the dispatcher."""
        logger.info("Stopping all channels...")

        # Stop dispatcher
        if self._dispatch_task:
            self._dispatch_task.cancel()
            try:
                await self._dispatch_task
            except asyncio.CancelledError:
                pass

        # 优先调用渠道 stop()，让渠道自身执行清理逻辑（如关闭网络连接、释放句柄）。
        for name, channel in self.channels.items():
            try:
                await channel.stop()
                logger.info(f"Stopped {name} channel")
            except Exception as e:
                logger.error(f"Error stopping {name}: {e}")

        # 兜底取消尚未退出的后台任务，确保 shutdown 不会悬挂。
        tasks_to_cancel = [task for task in self._channel_tasks.values() if not task.done()]
        for task in tasks_to_cancel:
            task.cancel()
        if tasks_to_cancel:
            await asyncio.gather(*tasks_to_cancel, return_exceptions=True)

        self._channel_tasks.clear()

    async def _dispatch_outbound(self) -> None:
        """Dispatch outbound messages to the appropriate channel."""
        logger.info("Outbound dispatcher started")

        while True:
            try:
                msg = await asyncio.wait_for(self.bus.consume_outbound(), timeout=1.0)

                channel = self.channels.get(msg.channel)
                if channel:
                    try:
                        await channel.send(msg)
                    except Exception as e:
                        logger.error(f"Error sending to {msg.channel}: {e}")
                else:
                    logger.warning(f"Unknown channel: {msg.channel}")

            except asyncio.TimeoutError:
                continue
            except asyncio.CancelledError:
                break

    def get_channel(self, name: str) -> BaseChannel | None:
        """Get a channel by name."""
        return self.channels.get(name)

    def get_status(self) -> dict[str, Any]:
        """Get status of all channels."""
        return {
            name: {"enabled": True, "running": channel.is_running}
            for name, channel in self.channels.items()
        }

    @property
    def enabled_channels(self) -> list[str]:
        """Get list of enabled channel names."""
        return list(self.channels.keys())
