"""BotClient — Bot 统一入口（Composition Root）

负责引导 Adapter 启动、组装 BotAPIClient、接通事件流、管理服务生命周期。
支持单/多适配器，多平台共享同一事件分发器。
作为应用编排层，允许依赖所有模块（core / plugin / service / api / adapter）。
"""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import List, Optional, Sequence, TYPE_CHECKING

from ncatbot.adapter import BaseAdapter, adapter_registry
from ncatbot.api import BotAPIClient
from ncatbot.api.qq import QQAPIClient
from ncatbot.core import AsyncEventDispatcher, HandlerDispatcher, flush_pending
from ncatbot.plugin import PluginLoader
from ncatbot.service import ServiceManager
from ncatbot.utils import get_log, setup_logging, ncatbot_config

if TYPE_CHECKING:
    pass

LOG = get_log("BotClient")


class BotClient:
    """Bot 生命周期管理器

    用法（零配置 — 从 config.yaml 自动加载适配器）::

        bot = BotClient()

        @bot.on("message.group")
        async def on_group_msg(event):
            await event.reply("hello")

        bot.run()

    用法（编程式指定适配器）::

        from ncatbot.adapter import NapCatAdapter
        bot = BotClient(adapters=[NapCatAdapter(config={...})])
        bot.run()
    """

    def __init__(
        self,
        adapter: Optional[BaseAdapter] = None,
        *,
        adapters: Optional[Sequence[BaseAdapter]] = None,
        plugin_dir: str = "plugins",
        debug: bool = False,
    ) -> None:
        setup_logging(debug=ncatbot_config.debug)

        # 构建适配器列表
        if adapters is not None:
            self._adapters: List[BaseAdapter] = list(adapters)
        elif adapter is not None:
            self._adapters = [adapter]
        else:
            # 从配置文件加载
            self._adapters = self._create_adapters_from_config()

        # 按 platform 去重检查
        seen: dict[str, str] = {}
        for a in self._adapters:
            p = getattr(a, "platform", a.name)
            if p in seen:
                raise ValueError(f"重复的平台 '{p}'：{a.name} 与 {seen[p]} 冲突")
            seen[p] = a.name

        # 向后兼容：_adapter 指向第一个
        self._adapter = self._adapters[0]

        self._api: Optional[BotAPIClient] = None
        self._dispatcher: Optional[AsyncEventDispatcher] = None
        self._handler_dispatcher: Optional[HandlerDispatcher] = None
        self._service_manager = ServiceManager()
        self._plugin_loader = PluginLoader(debug=debug)
        self._plugin_dir = Path(plugin_dir)
        self._debug = debug
        self._running = False
        self._listen_task: Optional[asyncio.Task] = None
        self._listen_tasks: List[asyncio.Task] = []

        # 待注册的 handler（在 run 之前通过 @bot.on() 收集）
        self._pending_handlers: list[tuple[str, object, dict]] = []

    @staticmethod
    def _create_adapters_from_config() -> List[BaseAdapter]:
        """从 config.yaml 的 adapters 列表创建适配器实例。"""
        cfg = ncatbot_config.config
        entries = [e for e in cfg.adapters if e.enabled]
        if not entries:
            raise ValueError(
                "配置文件中没有启用的适配器 (adapters 列表为空或全部 enabled: false)"
            )

        result: List[BaseAdapter] = []
        for entry in entries:
            adapter = adapter_registry.create(
                entry,
                bot_uin=cfg.bot_uin,
                websocket_timeout=cfg.websocket_timeout,
            )
            LOG.info(
                "从配置创建适配器: type=%s, platform=%s",
                entry.type,
                entry.platform or adapter.platform,
            )
            result.append(adapter)
        return result

    @property
    def plugin_loader(self) -> PluginLoader:
        return self._plugin_loader

    @property
    def service_manager(self) -> ServiceManager:
        return self._service_manager

    @property
    def api(self) -> BotAPIClient:
        if self._api is None:
            LOG.warning("API 不可用：BotClient 尚未启动")
            raise RuntimeError("API 不可用：BotClient 尚未启动")
        return self._api

    @property
    def dispatcher(self) -> AsyncEventDispatcher:
        if self._dispatcher is None:
            LOG.warning("Dispatcher 不可用：BotClient 尚未启动")
            raise RuntimeError("Dispatcher 不可用：BotClient 尚未启动")
        return self._dispatcher

    @property
    def handler_dispatcher(self) -> HandlerDispatcher:
        if self._handler_dispatcher is None:
            LOG.warning("HandlerDispatcher 不可用：BotClient 尚未启动")
            raise RuntimeError("HandlerDispatcher 不可用：BotClient 尚未启动")
        return self._handler_dispatcher

    # ---- 启动 ----

    def run(self) -> None:
        """同步阻塞启动（setup → connect → listen → shutdown）"""
        try:
            asyncio.run(self._run_blocking())
        except KeyboardInterrupt:
            LOG.info("收到 Ctrl+C，正在退出…")

    async def run_async(self) -> None:
        """异步非阻塞启动。

        完成 startup 后在后台启动监听，立即返回::

            bot = BotClient()
            await bot.run_async()
            # 此处 bot.api / bot.dispatcher 已可用
            async with bot.dispatcher.events(EventType.MESSAGE) as stream:
                async for event in stream:
                    ...
        """
        await self._startup()
        self._listen_task = asyncio.create_task(self._listen_forever())
        LOG.info("Bot 已就绪，后台监听已启动")

    async def shutdown(self) -> None:
        """关闭服务、Dispatcher 和 Adapter，释放资源。"""
        if not self._running:
            return
        self._running = False
        LOG.info("正在关闭…")

        # 优先断开适配器（释放端口等网络资源），使 listen 循环自然退出
        for adapter in self._adapters:
            try:
                await adapter.disconnect()
            except Exception as e:
                LOG.error("断开适配器 %s 异常: %s", adapter.name, e)

        # 等待所有监听 task 结束（已由 disconnect 触发退出）
        for task in self._listen_tasks:
            if not task.done():
                task.cancel()
        if self._listen_task and not self._listen_task.done():
            self._listen_task.cancel()
        for task in self._listen_tasks:
            try:
                await task
            except (asyncio.CancelledError, Exception):
                pass
        if self._listen_task:
            try:
                await self._listen_task
            except (asyncio.CancelledError, Exception):
                pass

        # 以下各步骤独立 try/except，避免互相阻塞
        try:
            await self._plugin_loader.stop_hot_reload()
            await self._plugin_loader.unload_all()
        except Exception as e:
            LOG.error("卸载插件异常: %s", e)

        try:
            await self._service_manager.close_all()
        except Exception as e:
            LOG.error("关闭服务异常: %s", e)

        try:
            if self._handler_dispatcher is not None:
                await self._handler_dispatcher.stop()
            if self._dispatcher is not None:
                await self._dispatcher.close()
        except Exception as e:
            LOG.error("关闭分发器异常: %s", e)

        LOG.info("已关闭")

    # ---- 内部 ----

    async def _run_blocking(self) -> None:
        """同步 run() 的内部实现：startup → 阻塞 listen → shutdown"""
        await self._startup()
        LOG.info("Bot 已就绪，开始监听事件（%d 个适配器）", len(self._adapters))
        try:
            if len(self._adapters) == 1:
                await self._adapters[0].listen()
            else:
                # 多适配器并行监听，单个适配器崩溃不影响其他适配器
                tasks = [
                    asyncio.create_task(self._safe_listen(a)) for a in self._adapters
                ]
                self._listen_tasks = tasks
                await asyncio.gather(*tasks)
        except asyncio.CancelledError:
            LOG.info("Bot 被取消")
        finally:
            await self.shutdown()

    async def _safe_listen(self, adapter: "BaseAdapter") -> None:
        """安全监听单个适配器，崩溃时仅记录日志而不影响其他适配器。"""
        try:
            await adapter.listen()
        except asyncio.CancelledError:
            raise
        except Exception as e:
            LOG.error("适配器 %s 监听异常，已停止: %s", adapter.name, e)

    async def _listen_forever(self) -> None:
        """后台监听 task，异常时自动 shutdown"""
        try:
            if len(self._adapters) == 1:
                await self._adapters[0].listen()
            else:
                tasks = [
                    asyncio.create_task(self._safe_listen(a)) for a in self._adapters
                ]
                self._listen_tasks = tasks
                await asyncio.gather(*tasks)
        except asyncio.CancelledError:
            pass
        except Exception as e:
            LOG.error("监听异常: %s", e)
        finally:
            await self.shutdown()

    async def _startup(self) -> None:
        """引导启动：adapter → API → dispatcher → 服务 → 插件 → 热重载"""
        await self._startup_core()
        await self._setup_plugins()
        self._running = True

    async def _startup_core(self) -> None:
        """核心启动（不含插件加载）：adapters → API → dispatcher → 服务"""
        await self._setup_adapters()
        self._setup_api()
        self._setup_dispatcher()
        self._setup_handler_dispatcher()
        await self._setup_services()

    async def _setup_adapters(self) -> None:
        """准备并连接所有 Adapter（含 pip 依赖检查）。"""
        skipped: list[BaseAdapter] = []
        for adapter in self._adapters:
            LOG.info(
                "正在启动 Adapter: %s (platform=%s)",
                adapter.name,
                getattr(adapter, "platform", "?"),
            )
            if not await adapter.ensure_deps():
                LOG.warning("适配器 %s 的 pip 依赖未满足，跳过该适配器", adapter.name)
                skipped.append(adapter)
                continue
            await adapter.setup()
            await adapter.connect()

        for adapter in skipped:
            self._adapters.remove(adapter)
        if not self._adapters:
            raise ValueError("所有适配器的依赖均未满足，无法启动")

    # 向后兼容别名
    async def _setup_adapter(self) -> None:
        await self._setup_adapters()

    def _setup_api(self) -> None:
        """从所有 Adapter 取出底层 IAPIClient，包装为平台 Client 后注册到 BotAPIClient。"""
        # 平台 → 包装类注册表
        _CLIENT_REGISTRY: dict[str, type] = {"qq": QQAPIClient}

        self._api = BotAPIClient()
        for adapter in self._adapters:
            platform = getattr(adapter, "platform", adapter.name)
            raw_api = adapter.get_api()
            if platform in _CLIENT_REGISTRY:
                client = _CLIENT_REGISTRY[platform](raw_api)
            else:
                client = raw_api
            self._api.register_platform(platform, client)
        LOG.info("BotAPIClient 已就绪（平台: %s）", list(self._api.platforms.keys()))

    def _setup_dispatcher(self) -> None:
        """创建事件分发器并接通所有 Adapter 的事件回调。"""
        self._dispatcher = AsyncEventDispatcher()
        for adapter in self._adapters:
            adapter.set_event_callback(self._dispatcher.callback)

    def _setup_handler_dispatcher(self) -> None:
        """创建 HandlerDispatcher 并订阅事件流。"""
        # 收集每个平台的 API
        platform_apis = {}
        for adapter in self._adapters:
            platform = getattr(adapter, "platform", adapter.name)
            platform_apis[platform] = adapter.get_api()

        self._handler_dispatcher = HandlerDispatcher(
            api=self._api,
            service_manager=self._service_manager,
            platform_apis=platform_apis,
        )
        self._handler_dispatcher.start(self.dispatcher)

    async def _setup_services(self) -> None:
        """注册并加载所有内置服务。"""
        self._service_manager.set_event_callback(self.dispatcher.callback)
        self._service_manager.register_builtin(debug=self._debug)
        await self._service_manager.load_all()

    async def _setup_plugins(self) -> None:
        """加载插件并配置热重载。"""

        self._plugin_loader.set_handler_dispatcher(self.handler_dispatcher)

        # 非插件模式：将模块级 @registrar.on_*() 收集的 handler 注册到 dispatcher
        flush_pending(self.handler_dispatcher, "__global__")

        def _inject_plugin_deps(plugin, manifest):
            plugin.services = self._service_manager
            plugin.api = self._api
            plugin._dispatcher = self._dispatcher
            plugin._plugin_loader = self._plugin_loader

        self._plugin_loader._on_plugin_init = _inject_plugin_deps
        await self._plugin_loader.load_builtin_plugins()
        await self._plugin_loader.load_all(self._plugin_dir)

        if self._debug and self._service_manager.has("file_watcher"):
            fw = self._service_manager.file_watcher
            fw.add_watch_dir(str(self._plugin_dir.resolve()))
            self._plugin_loader.setup_hot_reload(fw)
