"""内置 NapCat 适配器插件。

当前实现维持 MVP 范围，目标是跑通基础消息收发链路：
1. 作为客户端连接 NapCat / OneBot v11 WebSocket 服务。
2. 将入站消息事件转换为 Host 侧的 ``MessageDict``。
3. 将 Host 出站消息转换为 OneBot 动作并发送。

当前范围刻意收敛为：
- 单连接
- 文本、@、reply 基础转发
- 暂不处理 ``notice`` / ``meta_event`` 的完整语义归一化
- 暂不支持图片、语音、文件等复杂媒体
"""

from __future__ import annotations

from typing import Any, Dict, Mapping, Optional

import asyncio

from maibot_sdk import Adapter, MaiBotPlugin

from napcat_adapter.codec_inbound import NapCatInboundCodec
from napcat_adapter.codec_outbound import NapCatOutboundCodec
from napcat_adapter.config import NapCatPluginSettings
from napcat_adapter.filters import NapCatChatFilter
from napcat_adapter.qq_notice import NapCatNoticeCodec
from napcat_adapter.qq_queries import NapCatQueryService
from napcat_adapter.runtime_state import NapCatRuntimeStateManager
from napcat_adapter.transport import NapCatTransportClient


@Adapter(platform="qq", protocol="napcat", send_method="send_to_platform")
class NapCatAdapterPlugin(MaiBotPlugin):
    """NapCat 适配器 MVP 实现。"""

    def __init__(self) -> None:
        """初始化 NapCat 适配器插件实例。"""
        super().__init__()
        self._plugin_config: Dict[str, Any] = {}
        self._settings: Optional[NapCatPluginSettings] = None
        self._inbound_codec: Optional[NapCatInboundCodec] = None
        self._outbound_codec = NapCatOutboundCodec()
        self._chat_filter: Optional[NapCatChatFilter] = None
        self._query_service: Optional[NapCatQueryService] = None
        self._notice_codec: Optional[NapCatNoticeCodec] = None
        self._runtime_state: Optional[NapCatRuntimeStateManager] = None
        self._transport: Optional[NapCatTransportClient] = None

    def set_plugin_config(self, config: Dict[str, Any]) -> None:
        """设置插件配置内容。

        Args:
            config: Runner 注入的 ``config.toml`` 解析结果。
        """
        self._plugin_config = config if isinstance(config, dict) else {}

    async def on_load(self) -> None:
        """在插件加载时根据配置决定是否启动连接。"""
        await self._restart_connection_if_needed()

    async def on_unload(self) -> None:
        """在插件卸载时关闭连接并清理运行时状态。"""
        await self._stop_connection()

    async def on_config_update(self, new_config: Dict[str, Any], version: str) -> None:
        """在配置更新后重载连接状态。

        Args:
            new_config: 最新的插件配置。
            version: 配置版本号。
        """
        self.set_plugin_config(new_config)
        self._settings = None
        if version:
            self.ctx.logger.debug(f"NapCat 适配器收到配置更新通知: {version}")
        await self._restart_connection_if_needed()

    async def send_to_platform(
        self,
        message: Dict[str, Any],
        route: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """将 Host 出站消息发送到 NapCat。

        Args:
            message: Host 侧标准 ``MessageDict``。
            route: Platform IO 生成的路由信息。
            metadata: Platform IO 附带的投递元数据。
            **kwargs: 预留的扩展参数。

        Returns:
            Dict[str, Any]: 标准化后的发送结果。
        """
        del metadata
        del kwargs

        self._ensure_runtime_components()
        transport = self._transport
        if transport is None:
            return {"success": False, "error": "NapCat transport is not initialized"}

        try:
            action_name, params = self._outbound_codec.build_outbound_action(message, route or {})
            response = await transport.call_action(action_name, params)
        except Exception as exc:
            return {"success": False, "error": str(exc)}

        if str(response.get("status", "")).lower() != "ok":
            return {
                "success": False,
                "error": str(response.get("wording") or response.get("message") or "NapCat send failed"),
                "metadata": {"retcode": response.get("retcode")},
            }

        response_data = response.get("data", {})
        external_message_id = ""
        if isinstance(response_data, Mapping):
            external_message_id = str(response_data.get("message_id") or "")

        return {
            "success": True,
            "external_message_id": external_message_id or None,
            "metadata": {"action": action_name},
        }

    def _ensure_runtime_components(self) -> None:
        """确保运行时依赖对象已经完成初始化。"""
        if self._chat_filter is None:
            self._chat_filter = NapCatChatFilter(self.ctx.logger)

        if self._transport is None:
            self._transport = NapCatTransportClient(
                logger=self.ctx.logger,
                on_connection_opened=self._bootstrap_adapter_runtime_state,
                on_connection_closed=self._handle_transport_disconnected,
                on_payload=self._handle_transport_payload,
            )

        if self._query_service is None:
            self._query_service = NapCatQueryService(self.ctx.logger, self._transport)

        if self._inbound_codec is None:
            self._inbound_codec = NapCatInboundCodec(self.ctx.logger, self._query_service)

        if self._notice_codec is None:
            self._notice_codec = NapCatNoticeCodec(self.ctx.logger, self._query_service)

        if self._runtime_state is None:
            self._runtime_state = NapCatRuntimeStateManager(self.ctx.adapter, self.ctx.logger)

    def _reload_settings(self) -> NapCatPluginSettings:
        """重新解析当前插件配置。

        Returns:
            NapCatPluginSettings: 最新的规范化配置。
        """
        self._settings = NapCatPluginSettings.from_mapping(self._plugin_config, self.ctx.logger)
        return self._settings

    async def _restart_connection_if_needed(self) -> None:
        """根据当前配置重启连接循环。"""
        self._ensure_runtime_components()
        settings = self._reload_settings()

        await self._stop_connection()
        if not settings.should_connect():
            self.ctx.logger.info("NapCat 适配器保持空闲状态，因为插件或配置未启用")
            return
        if not settings.validate(self.ctx.logger):
            return

        transport = self._transport
        assert transport is not None
        if not transport.is_available():
            self.ctx.logger.error("NapCat 适配器依赖 aiohttp，但当前环境未安装该依赖")
            return

        transport.configure(settings.napcat_server)
        await transport.start()

    async def _stop_connection(self) -> None:
        """停止当前连接。"""
        transport = self._transport
        if transport is not None:
            await transport.stop()
            return

        runtime_state = self._runtime_state
        if runtime_state is not None:
            await runtime_state.report_disconnected()

    async def _handle_transport_payload(self, payload: Dict[str, Any]) -> None:
        """处理来自传输层的非 echo 载荷。

        Args:
            payload: NapCat 推送的原始事件数据。
        """
        post_type = str(payload.get("post_type") or "").strip()
        if post_type == "message":
            await self._handle_inbound_message(payload)
            return
        if post_type == "notice":
            await self._handle_notice_event(payload)
            return
        if post_type == "meta_event":
            await self._handle_meta_event(payload)

    async def _handle_inbound_message(self, payload: Dict[str, Any]) -> None:
        """处理单条 NapCat 入站消息并注入 Host。

        Args:
            payload: NapCat / OneBot 推送的原始消息事件。
        """
        self._ensure_runtime_components()
        settings = self._settings or self._reload_settings()
        chat_filter = self._chat_filter
        inbound_codec = self._inbound_codec
        runtime_state = self._runtime_state
        assert chat_filter is not None
        assert inbound_codec is not None
        assert runtime_state is not None

        self_id = str(payload.get("self_id") or "").strip()
        if self_id:
            await runtime_state.report_connected(self_id, settings.napcat_server)

        sender = payload.get("sender", {})
        if not isinstance(sender, Mapping):
            sender = {}

        sender_user_id = str(payload.get("user_id") or sender.get("user_id") or "").strip()
        if not sender_user_id:
            return

        group_id = str(payload.get("group_id") or "").strip()
        if self_id and sender_user_id == self_id and settings.filters.ignore_self_message:
            return
        if not chat_filter.is_inbound_chat_allowed(sender_user_id, group_id, settings.chat):
            return

        message_dict = await inbound_codec.build_message_dict(payload, self_id, sender_user_id, sender)
        route_metadata: Dict[str, Any] = {}
        if self_id:
            route_metadata["self_id"] = self_id
        if settings.napcat_server.connection_id:
            route_metadata["connection_id"] = settings.napcat_server.connection_id

        external_message_id = str(payload.get("message_id") or "").strip()
        accepted = await self.ctx.adapter.receive_external_message(
            message_dict,
            route_metadata=route_metadata,
            external_message_id=external_message_id,
            dedupe_key=external_message_id,
        )
        if not accepted:
            self.ctx.logger.debug(f"Host 丢弃了 NapCat 入站消息: {external_message_id or '无消息 ID'}")

    async def _handle_notice_event(self, payload: Dict[str, Any]) -> None:
        """处理 NapCat ``notice`` 事件并注入 Host。

        Args:
            payload: NapCat 推送的通知事件。
        """
        self._ensure_runtime_components()
        notice_codec = self._notice_codec
        runtime_state = self._runtime_state
        settings = self._settings or self._reload_settings()
        assert notice_codec is not None
        assert runtime_state is not None

        self_id = str(payload.get("self_id") or "").strip()
        if self_id:
            await runtime_state.report_connected(self_id, settings.napcat_server)

        message_dict = await notice_codec.build_notice_message_dict(payload)
        if message_dict is None:
            return

        route_metadata: Dict[str, Any] = {}
        if self_id:
            route_metadata["self_id"] = self_id
        if settings.napcat_server.connection_id:
            route_metadata["connection_id"] = settings.napcat_server.connection_id

        external_message_id = str(payload.get("message_id") or payload.get("notice_type") or "").strip()
        accepted = await self.ctx.adapter.receive_external_message(
            message_dict,
            route_metadata=route_metadata,
            external_message_id=external_message_id or None,
            dedupe_key=external_message_id or None,
        )
        if not accepted:
            self.ctx.logger.debug(f"Host 丢弃了 NapCat 通知事件: {external_message_id or '无消息 ID'}")

    async def _handle_meta_event(self, payload: Dict[str, Any]) -> None:
        """处理 NapCat ``meta_event`` 事件。

        Args:
            payload: NapCat 推送的元事件。
        """
        self._ensure_runtime_components()
        notice_codec = self._notice_codec
        runtime_state = self._runtime_state
        settings = self._settings or self._reload_settings()
        assert notice_codec is not None
        assert runtime_state is not None

        self_id = str(payload.get("self_id") or "").strip()
        if self_id:
            await runtime_state.report_connected(self_id, settings.napcat_server)

        await notice_codec.handle_meta_event(payload)

    async def _bootstrap_adapter_runtime_state(self) -> None:
        """在连接建立后主动获取账号信息并激活适配器路由。"""
        transport = self._transport
        query_service = self._query_service
        runtime_state = self._runtime_state
        settings = self._settings or self._reload_settings()
        if transport is None or query_service is None or runtime_state is None:
            return

        max_attempts = 3
        last_error: Optional[Exception] = None
        for attempt in range(1, max_attempts + 1):
            try:
                login_info = await query_service.get_login_info()
                self_id = self._extract_self_id_from_login_response(login_info)
                await runtime_state.report_connected(self_id, settings.napcat_server)
                return
            except asyncio.CancelledError:
                raise
            except Exception as exc:
                last_error = exc
                self.ctx.logger.warning(
                    f"NapCat 适配器获取登录信息失败，第 {attempt}/{max_attempts} 次重试: {exc}"
                )
                if attempt < max_attempts:
                    await asyncio.sleep(1.0)

        if last_error is not None:
            self.ctx.logger.error(f"NapCat 适配器未能完成路由激活，连接将保持只接收状态: {last_error}")

    async def _handle_transport_disconnected(self) -> None:
        """处理传输层断开事件。"""
        runtime_state = self._runtime_state
        if runtime_state is not None:
            await runtime_state.report_disconnected()

    @staticmethod
    def _extract_self_id_from_login_response(response: Optional[Dict[str, Any]]) -> str:
        """从 ``get_login_info`` 查询结果中提取当前账号 ID。

        Args:
            response: NapCat 返回的登录信息字典。

        Returns:
            str: 规范化后的账号 ID 字符串。

        Raises:
            ValueError: 当响应中缺少有效账号 ID 时抛出。
        """
        if not isinstance(response, Mapping):
            raise ValueError("get_login_info 响应缺少 data 字段")

        self_id = str(response.get("user_id") or "").strip()
        if not self_id:
            raise ValueError("get_login_info 响应缺少有效的 user_id")
        return self_id


def create_plugin() -> NapCatAdapterPlugin:
    """创建插件实例。

    Returns:
        NapCatAdapterPlugin: NapCat 内置适配器插件实例。
    """
    return NapCatAdapterPlugin()
