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

from __future__ import annotations

import asyncio
from typing import Any

from loguru import logger

from asedclaw.bus.events import OutboundMessage
from asedclaw.bus.queue import MessageBus
from asedclaw.channels.base import BaseChannel
from asedclaw.config.schema import Config


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

    Responsibilities:
    - Initialize enabled channels (Telegram, WhatsApp, 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._init_channels()

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

        # Telegram channel
        if self.config.channels.telegram.enabled:
            try:
                from asedclaw.channels.telegram import TelegramChannel
                self.channels["telegram"] = TelegramChannel(
                    self.config.channels.telegram,
                    self.bus,
                    groq_api_key=self.config.providers.groq.api_key,
                )
                logger.info("Telegram channel enabled")
            except ImportError as e:
                logger.warning("Telegram channel not available: {}", e)

        # WhatsApp channel
        if self.config.channels.whatsapp.enabled:
            try:
                from asedclaw.channels.whatsapp import WhatsAppChannel
                self.channels["whatsapp"] = WhatsAppChannel(
                    self.config.channels.whatsapp, self.bus
                )
                logger.info("WhatsApp channel enabled")
            except ImportError as e:
                logger.warning("WhatsApp channel not available: {}", e)

        # Discord channel
        if self.config.channels.discord.enabled:
            try:
                from asedclaw.channels.discord import DiscordChannel
                self.channels["discord"] = DiscordChannel(
                    self.config.channels.discord, self.bus
                )
                logger.info("Discord channel enabled")
            except ImportError as e:
                logger.warning("Discord channel not available: {}", e)

        # Feishu channel
        if self.config.channels.feishu.enabled:
            try:
                from asedclaw.channels.feishu import FeishuChannel
                self.channels["feishu"] = FeishuChannel(
                    self.config.channels.feishu, self.bus,
                    groq_api_key=self.config.providers.groq.api_key,
                )
                logger.info("Feishu channel enabled")
            except ImportError as e:
                logger.warning("Feishu channel not available: {}", e)

        # Mochat channel
        if self.config.channels.mochat.enabled:
            try:
                from asedclaw.channels.mochat import MochatChannel

                self.channels["mochat"] = MochatChannel(
                    self.config.channels.mochat, self.bus
                )
                logger.info("Mochat channel enabled")
            except ImportError as e:
                logger.warning("Mochat channel not available: {}", e)

        # DingTalk channel
        if self.config.channels.dingtalk.enabled:
            try:
                from asedclaw.channels.dingtalk import DingTalkChannel
                self.channels["dingtalk"] = DingTalkChannel(
                    self.config.channels.dingtalk, self.bus
                )
                logger.info("DingTalk channel enabled")
            except ImportError as e:
                logger.warning("DingTalk channel not available: {}", e)

        # Email channel
        if self.config.channels.email.enabled:
            try:
                from asedclaw.channels.email import EmailChannel
                self.channels["email"] = EmailChannel(
                    self.config.channels.email, self.bus
                )
                logger.info("Email channel enabled")
            except ImportError as e:
                logger.warning("Email channel not available: {}", e)

        # Slack channel
        if self.config.channels.slack.enabled:
            try:
                from asedclaw.channels.slack import SlackChannel
                self.channels["slack"] = SlackChannel(
                    self.config.channels.slack, self.bus
                )
                logger.info("Slack channel enabled")
            except ImportError as e:
                logger.warning("Slack channel not available: {}", e)

        # QQ channel
        if self.config.channels.qq.enabled:
            try:
                from asedclaw.channels.qq import QQChannel
                self.channels["qq"] = QQChannel(
                    self.config.channels.qq,
                    self.bus,
                )
                logger.info("QQ channel enabled")
            except ImportError as e:
                logger.warning("QQ channel not available: {}", e)

        # Matrix channel
        if self.config.channels.matrix.enabled:
            try:
                from asedclaw.channels.matrix import MatrixChannel
                self.channels["matrix"] = MatrixChannel(
                    self.config.channels.matrix,
                    self.bus,
                )
                logger.info("Matrix channel enabled")
            except ImportError as e:
                logger.warning("Matrix channel not available: {}", e)

        # ASED channel (SuperAgent IM for Web user ↔ Bot chat)
        if self.config.channels.im.enabled:
            try:
                from asedclaw.channels.ased import IMChannel
                self.channels["ased"] = IMChannel(
                    self.config.channels.im,
                    self.bus,
                )
                logger.info("ASED channel enabled")
            except ImportError as e:
                logger.warning("ASED channel not available: {}", e)

        self._validate_allow_from()

    def _validate_allow_from(self) -> None:
        for name, ch in self.channels.items():
            if name == "ased":
                continue
            if getattr(ch.config, "allow_from", None) == []:
                raise SystemExit(
                    f'Error: "{name}" has empty allowFrom (denies all). '
                    f'Set ["*"] to allow everyone, or add specific user IDs.'
                )

    async def _start_channel(self, name: str, channel: BaseChannel) -> None:
        """Start a channel and log any exceptions."""
        try:
            await channel.start()
        except Exception as e:
            logger.error("Failed to start channel {}: {}", name, e)

    async def start_all(self) -> None:
        """Start all channels and the outbound dispatcher."""
        if not self.channels:
            logger.warning("No channels enabled")
            return

        # Start outbound dispatcher
        self._dispatch_task = asyncio.create_task(self._dispatch_outbound())

        # Start channels
        tasks = []
        for name, channel in self.channels.items():
            logger.info("Starting {} channel...", name)
            tasks.append(asyncio.create_task(self._start_channel(name, channel)))

        # Wait for all to complete (they should run forever)
        await asyncio.gather(*tasks, return_exceptions=True)

    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 all channels
        for name, channel in self.channels.items():
            try:
                await channel.stop()
                logger.info("Stopped {} channel", name)
            except Exception as e:
                logger.error("Error stopping {}: {}", name, e)

    _PROGRESS_BATCH_DELAY = 0.3  # seconds to wait before flushing batched progress

    async def _dispatch_outbound(self) -> None:
        """Dispatch outbound messages, batching consecutive progress chunks."""
        logger.info("Outbound dispatcher started")

        pending_progress: dict[str, OutboundMessage] = {}
        flush_task: asyncio.Task | None = None

        async def _flush_progress() -> None:
            await asyncio.sleep(self._PROGRESS_BATCH_DELAY)
            for key in list(pending_progress):
                merged = pending_progress.pop(key, None)
                if merged and merged.content:
                    channel = self.channels.get(merged.channel)
                    if channel:
                        try:
                            await channel.send(merged)
                        except Exception as e:
                            logger.error("Error sending batched progress to {}: {}", merged.channel, e)

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

                is_progress = msg.metadata.get("_progress", False)
                is_tool_hint = msg.metadata.get("_tool_hint", False)

                if is_progress:
                    if is_tool_hint and not self.config.channels.send_tool_hints:
                        continue
                    if not is_tool_hint and not self.config.channels.send_progress:
                        continue

                    # ased channel handles streaming internally—dispatch each chunk
                    # immediately so the frontend gets real-time character-by-character output.
                    if msg.channel == "ased":
                        channel = self.channels.get("ased")
                        if channel:
                            try:
                                await channel.send(msg)
                            except Exception as e:
                                logger.error("Error sending stream chunk to ased: {}", e)
                        continue

                    batch_key = f"{msg.channel}:{msg.chat_id}"
                    existing = pending_progress.get(batch_key)
                    if existing and not is_tool_hint:
                        existing.content += msg.content
                    else:
                        pending_progress[batch_key] = OutboundMessage(
                            channel=msg.channel,
                            chat_id=msg.chat_id,
                            content=msg.content,
                            metadata=dict(msg.metadata),
                        )

                    if flush_task is None or flush_task.done():
                        flush_task = asyncio.create_task(_flush_progress())
                    continue

                if pending_progress:
                    if flush_task and not flush_task.done():
                        flush_task.cancel()
                        try:
                            await flush_task
                        except asyncio.CancelledError:
                            pass
                    for key in list(pending_progress):
                        merged = pending_progress.pop(key, None)
                        if merged and merged.content:
                            channel = self.channels.get(merged.channel)
                            if channel:
                                try:
                                    await channel.send(merged)
                                except Exception as e:
                                    logger.error("Error sending batched progress to {}: {}", merged.channel, e)

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

            except asyncio.TimeoutError:
                if pending_progress and (flush_task is None or flush_task.done()):
                    flush_task = asyncio.create_task(_flush_progress())
                continue
            except asyncio.CancelledError:
                for merged in pending_progress.values():
                    if merged and merged.content:
                        channel = self.channels.get(merged.channel)
                        if channel:
                            try:
                                await channel.send(merged)
                            except Exception:
                                pass
                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())
