from __future__ import annotations
import typing
import asyncio
import traceback
import datetime
import json
import time

import aiocqhttp
import pydantic

import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ...utils import image
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger


_GROUP_NAME_CACHE_TTL_SECONDS = 3600
_GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS = 60
_GROUP_NAME_LOOKUP_TIMEOUT_SECONDS = 2
_GROUP_MEMBER_INFO_CACHE_TTL_SECONDS = 86400
_GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS = 600
_GROUP_MEMBER_INFO_LOOKUP_TIMEOUT_SECONDS = 2
_LOOKUP_CACHE_MAX = 4096


def _normalize_base64_payload(value: str) -> str:
    if value.startswith('base64://'):
        return value.removeprefix('base64://')
    if value.startswith('data:') and ';base64,' in value:
        return value.split(';base64,', 1)[1]
    return value


def _get_field(data: dict, key: str, default: str = '') -> str:
    value = data.get(key)
    if value is None:
        return default
    return str(value)


def _get_group_member_name(sender: dict) -> str:
    return _get_field(sender, 'card') or _get_field(sender, 'nickname') or _get_field(sender, 'user_id')


def _get_group_name_placeholder(group_id: typing.Union[int, str]) -> str:
    return f'Group {group_id}'


class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
    @staticmethod
    async def yiri2target(
        message_chain: platform_message.MessageChain,
    ) -> typing.Tuple[list, int, datetime.datetime]:
        msg_list = aiocqhttp.Message()

        msg_id = 0
        msg_time = None

        for msg in message_chain:
            if type(msg) is platform_message.Plain:
                msg_list.append(aiocqhttp.MessageSegment.text(msg.text))
            elif type(msg) is platform_message.Source:
                msg_id = msg.id
                msg_time = msg.time
            elif type(msg) is platform_message.Image:
                arg = ''
                if msg.base64:
                    arg = _normalize_base64_payload(msg.base64)
                    msg_list.append(aiocqhttp.MessageSegment.image(f'base64://{arg}'))
                elif msg.url:
                    arg = msg.url
                    msg_list.append(aiocqhttp.MessageSegment.image(arg))
                elif msg.path:
                    arg = msg.path
                    msg_list.append(aiocqhttp.MessageSegment.image(arg))
            elif type(msg) is platform_message.At:
                msg_list.append(aiocqhttp.MessageSegment.at(msg.target))
            elif type(msg) is platform_message.AtAll:
                msg_list.append(aiocqhttp.MessageSegment.at('all'))
            elif type(msg) is platform_message.Voice:
                arg = ''
                if msg.base64:
                    arg = _normalize_base64_payload(msg.base64)
                    msg_list.append(aiocqhttp.MessageSegment.record(f'base64://{arg}'))
                elif msg.url:
                    arg = msg.url
                    msg_list.append(aiocqhttp.MessageSegment.record(arg))
                elif msg.path:
                    arg = msg.path
                    msg_list.append(aiocqhttp.MessageSegment.record(msg.path))
            elif type(msg) is platform_message.Forward:
                for node in msg.node_list:
                    msg_list.extend((await AiocqhttpMessageConverter.yiri2target(node.message_chain))[0])
            elif isinstance(msg, platform_message.File):
                file = msg.url or msg.path
                if not file and msg.base64:
                    file = f'base64://{_normalize_base64_payload(msg.base64)}'
                msg_list.append({'type': 'file', 'data': {'file': file, 'name': msg.name}})
            elif isinstance(msg, platform_message.Face):
                if msg.face_type == 'face':
                    msg_list.append(aiocqhttp.MessageSegment.face(msg.face_id))
                elif msg.face_type == 'rps':
                    msg_list.append(aiocqhttp.MessageSegment.rps())
                elif msg.face_type == 'dice':
                    msg_list.append(aiocqhttp.MessageSegment.dice())

            else:
                msg_list.append(aiocqhttp.MessageSegment.text(str(msg)))

        return msg_list, msg_id, msg_time

    @staticmethod
    async def target2yiri(message: str, message_id: int = -1, bot: aiocqhttp.CQHttp = None):
        message = aiocqhttp.Message(message)

        def get_face_name(face_id):
            face_code_dict = {
                '2': '好色',
                '4': '得意',
                '5': '流泪',
                '8': '睡',
                '9': '大哭',
                '10': '尴尬',
                '12': '调皮',
                '14': '微笑',
                '16': '酷',
                '21': '可爱',
                '23': '傲慢',
                '24': '饥饿',
                '25': '困',
                '26': '惊恐',
                '27': '流汗',
                '28': '憨笑',
                '29': '悠闲',
                '30': '奋斗',
                '32': '疑问',
                '33': '嘘',
                '34': '晕',
                '38': '敲打',
                '39': '再见',
                '41': '发抖',
                '42': '爱情',
                '43': '跳跳',
                '49': '拥抱',
                '53': '蛋糕',
                '60': '咖啡',
                '63': '玫瑰',
                '66': '爱心',
                '74': '太阳',
                '75': '月亮',
                '76': '赞',
                '78': '握手',
                '79': '胜利',
                '85': '飞吻',
                '89': '西瓜',
                '96': '冷汗',
                '97': '擦汗',
                '98': '抠鼻',
                '99': '鼓掌',
                '100': '糗大了',
                '101': '坏笑',
                '102': '左哼哼',
                '103': '右哼哼',
                '104': '哈欠',
                '106': '委屈',
                '109': '左亲亲',
                '111': '可怜',
                '116': '示爱',
                '118': '抱拳',
                '120': '拳头',
                '122': '爱你',
                '123': 'NO',
                '124': 'OK',
                '125': '转圈',
                '129': '挥手',
                '144': '喝彩',
                '147': '棒棒糖',
                '171': '茶',
                '173': '泪奔',
                '174': '无奈',
                '175': '卖萌',
                '176': '小纠结',
                '179': 'doge',
                '180': '惊喜',
                '181': '骚扰',
                '182': '笑哭',
                '183': '我最美',
                '201': '点赞',
                '203': '托脸',
                '212': '托腮',
                '214': '啵啵',
                '219': '蹭一蹭',
                '222': '抱抱',
                '227': '拍手',
                '232': '佛系',
                '240': '喷脸',
                '243': '甩头',
                '246': '加油抱抱',
                '262': '脑阔疼',
                '264': '捂脸',
                '265': '辣眼睛',
                '266': '哦哟',
                '267': '头秃',
                '268': '问号脸',
                '269': '暗中观察',
                '270': 'emm',
                '271': '吃瓜',
                '272': '呵呵哒',
                '273': '我酸了',
                '277': '汪汪',
                '278': '汗',
                '281': '无眼笑',
                '282': '敬礼',
                '284': '面无表情',
                '285': '摸鱼',
                '287': '哦',
                '289': '睁眼',
                '290': '敲开心',
                '293': '摸锦鲤',
                '294': '期待',
                '297': '拜谢',
                '298': '元宝',
                '299': '牛啊',
                '305': '右亲亲',
                '306': '牛气冲天',
                '307': '喵喵',
                '314': '仔细分析',
                '315': '加油',
                '318': '崇拜',
                '319': '比心',
                '320': '庆祝',
                '322': '拒绝',
                '324': '吃糖',
                '326': '生气',
            }
            return face_code_dict.get(face_id, '')

        async def process_message_data(msg_data, reply_list):
            if msg_data['type'] == 'image':
                image_base64, image_format = await image.qq_image_url_to_base64(msg_data['data']['url'])
                reply_list.append(
                    platform_message.Image(
                        url=msg_data['data']['url'], base64=f'data:image/{image_format};base64,{image_base64}'
                    )
                )

            elif msg_data['type'] == 'text':
                reply_list.append(platform_message.Plain(text=msg_data['data']['text']))

            elif msg_data['type'] == 'forward':  # 这里来应该传入转发消息组，暂时传入Quote
                for forward_msg_datas in msg_data['data']['content']:
                    for forward_msg_data in forward_msg_datas['message']:
                        await process_message_data(forward_msg_data, reply_list)

            elif msg_data['type'] == 'at':
                if msg_data['data']['qq'] == 'all':
                    reply_list.append(platform_message.AtAll())
                else:
                    reply_list.append(
                        platform_message.At(
                            target=msg_data['data']['qq'],
                        )
                    )

        yiri_msg_list = []

        yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))

        for msg in message:
            reply_list = []
            if msg.type == 'at':
                if msg.data['qq'] == 'all':
                    yiri_msg_list.append(platform_message.AtAll())
                else:
                    yiri_msg_list.append(
                        platform_message.At(
                            target=msg.data['qq'],
                        )
                    )
            elif msg.type == 'text':
                yiri_msg_list.append(platform_message.Plain(text=msg.data['text']))
            elif msg.type == 'image':
                emoji_id = msg.data.get('emoji_package_id', None)
                if emoji_id:
                    face_id = emoji_id
                    face_name = msg.data.get('summary', '')
                    image_msg = platform_message.Face(face_id=face_id, face_name=face_name)
                else:
                    image_base64, image_format = await image.qq_image_url_to_base64(msg.data['url'])
                    image_msg = platform_message.Image(
                        url=msg.data['url'], base64=f'data:image/{image_format};base64,{image_base64}'
                    )
                yiri_msg_list.append(image_msg)
            elif msg.type == 'forward':
                # 暂时不太合理
                # msg_datas = await bot.get_msg(message_id=message_id)
                # print(msg_datas)
                # for msg_data in msg_datas["message"]:
                #     await process_message_data(msg_data, yiri_msg_list)
                pass

            elif msg.type == 'reply':  # 此处处理引用消息传入Quote
                msg_datas = await bot.get_msg(message_id=msg.data['id'])

                for msg_data in msg_datas['message']:
                    await process_message_data(msg_data, reply_list)

                reply_msg = platform_message.Quote(
                    message_id=msg.data['id'], sender_id=msg_datas['user_id'], origin=reply_list
                )
                yiri_msg_list.append(reply_msg)

            elif msg.type == 'file':
                pass
                # file_name = msg.data['file']
                # file_id = msg.data['file_id']
                # file_data = await bot.get_file(file_id=file_id)
                # file_name = file_data.get('file_name')
                # file_path = file_data.get('file')
                # _ = file_path
                # file_url = file_data.get('file_url')
                # file_size = file_data.get('file_size')
                # yiri_msg_list.append(platform_message.File(id=file_id, name=file_name,url=file_url,size=file_size))
            elif msg.type == 'face':
                face_id = msg.data['id']
                face_name = msg.data['raw']['faceText']
                if not face_name:
                    face_name = get_face_name(face_id)
                yiri_msg_list.append(platform_message.Face(face_id=int(face_id), face_name=face_name.replace('/', '')))
            elif msg.type == 'rps':
                face_id = msg.data['result']
                yiri_msg_list.append(platform_message.Face(face_type='rps', face_id=int(face_id), face_name='猜拳'))
            elif msg.type == 'dice':
                face_id = msg.data['result']
                yiri_msg_list.append(platform_message.Face(face_type='dice', face_id=int(face_id), face_name='骰子'))
            elif msg.type == 'json':
                try:
                    raw = msg.data.get('data', {})
                    if isinstance(raw, str):
                        raw = json.loads(raw)
                    if isinstance(raw, dict):
                        _meta = raw.get('meta', {}) or {}
                        if isinstance(_meta, dict):
                            _detail = _meta.get('detail_1') or _meta.get('music') or _meta.get('news') or {}
                        else:
                            _detail = {}
                        if isinstance(_detail, dict):
                            preview = _detail.get('preview', '')
                            title = _detail.get('desc', '') or _detail.get('title', '')
                            url = _detail.get('qqdocurl', '') or _detail.get('jumpUrl', '')
                        else:
                            preview = title = url = ''
                        text = ' '.join([f'[{raw.get("app", "")}]', preview, title, url]).strip()
                        yiri_msg_list.append(platform_message.Plain(text=text or '[收到一张JSON卡片]'))
                    else:
                        yiri_msg_list.append(platform_message.Plain(text=str(raw)))
                except Exception:
                    yiri_msg_list.append(platform_message.Plain(text='[收到一张JSON卡片]'))

        chain = platform_message.MessageChain(yiri_msg_list)

        return chain


class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
    def __init__(self):
        self._group_name_cache: dict[typing.Union[int, str], tuple[str, float]] = {}
        self._group_name_negative_cache: dict[typing.Union[int, str], float] = {}
        self._group_member_info_cache: dict[
            tuple[typing.Union[int, str], typing.Union[int, str]], tuple[dict, float]
        ] = {}
        self._group_member_info_negative_cache: dict[tuple[typing.Union[int, str], typing.Union[int, str]], float] = {}
        self._last_cache_cleanup = 0.0

    def _prune_caches(self, now: float) -> None:
        caches = (
            self._group_name_cache,
            self._group_name_negative_cache,
            self._group_member_info_cache,
            self._group_member_info_negative_cache,
        )
        if now - self._last_cache_cleanup < 60 and all(len(cache) <= _LOOKUP_CACHE_MAX for cache in caches):
            return
        self._last_cache_cleanup = now
        for cache in caches:
            for key, value in tuple(cache.items()):
                expires_at = value[1] if isinstance(value, tuple) else value
                if expires_at <= now:
                    cache.pop(key, None)
            while len(cache) > _LOOKUP_CACHE_MAX:
                cache.pop(next(iter(cache)), None)

    def clear(self) -> None:
        self._group_name_cache.clear()
        self._group_name_negative_cache.clear()
        self._group_member_info_cache.clear()
        self._group_member_info_negative_cache.clear()

    @staticmethod
    async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int):
        return event.source_platform_object

    async def _get_group_name(self, group_id: typing.Union[int, str], bot=None) -> str:
        now = time.monotonic()
        self._prune_caches(now)
        if group_id in self._group_name_cache:
            group_name, expires_at = self._group_name_cache[group_id]
            if expires_at > now:
                return group_name
            del self._group_name_cache[group_id]
        if group_id in self._group_name_negative_cache:
            expires_at = self._group_name_negative_cache[group_id]
            if expires_at > now:
                return ''
            del self._group_name_negative_cache[group_id]
        if bot is None:
            return ''
        try:
            group_info = await asyncio.wait_for(
                bot.get_group_info(group_id=group_id),
                timeout=_GROUP_NAME_LOOKUP_TIMEOUT_SECONDS,
            )
        except Exception:
            self._group_name_negative_cache[group_id] = now + _GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS
            return ''
        group_name = _get_field(group_info, 'group_name') if isinstance(group_info, dict) else ''
        if group_name:
            self._group_name_cache[group_id] = (group_name, now + _GROUP_NAME_CACHE_TTL_SECONDS)
            self._group_name_negative_cache.pop(group_id, None)
        else:
            self._group_name_negative_cache[group_id] = now + _GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS
        return group_name

    async def _get_group_member_info(
        self,
        group_id: typing.Union[int, str],
        user_id: typing.Union[int, str],
        bot=None,
    ) -> dict:
        now = time.monotonic()
        self._prune_caches(now)
        cache_key = (group_id, user_id)
        if cache_key in self._group_member_info_cache:
            member_info, expires_at = self._group_member_info_cache[cache_key]
            if expires_at > now:
                return member_info
            del self._group_member_info_cache[cache_key]
        if cache_key in self._group_member_info_negative_cache:
            expires_at = self._group_member_info_negative_cache[cache_key]
            if expires_at > now:
                return {}
            del self._group_member_info_negative_cache[cache_key]
        if bot is None:
            return {}
        try:
            member_info = await asyncio.wait_for(
                bot.get_group_member_info(group_id=group_id, user_id=user_id),
                timeout=_GROUP_MEMBER_INFO_LOOKUP_TIMEOUT_SECONDS,
            )
        except Exception:
            self._group_member_info_negative_cache[cache_key] = now + _GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS
            return {}
        if isinstance(member_info, dict) and member_info:
            self._group_member_info_cache[cache_key] = (
                member_info,
                now + _GROUP_MEMBER_INFO_CACHE_TTL_SECONDS,
            )
            self._group_member_info_negative_cache.pop(cache_key, None)
            return member_info
        self._group_member_info_negative_cache[cache_key] = now + _GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS
        return {}

    async def target2yiri(self, event: aiocqhttp.Event, bot=None):
        yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id, bot)

        if event.message_type == 'group':
            permission = 'MEMBER'
            group_name = await self._get_group_name(event.group_id, bot) or _get_group_name_placeholder(event.group_id)
            special_title = _get_field(event.sender, 'title')
            if not special_title:
                member_info = await self._get_group_member_info(event.group_id, event.sender['user_id'], bot)
                special_title = _get_field(member_info, 'title')

            if 'role' in event.sender:
                if event.sender['role'] == 'admin':
                    permission = 'ADMINISTRATOR'
                elif event.sender['role'] == 'owner':
                    permission = 'OWNER'
            converted_event = platform_events.GroupMessage(
                sender=platform_entities.GroupMember(
                    id=event.sender['user_id'],  # message_seq 放哪？
                    member_name=_get_group_member_name(event.sender),
                    permission=permission,
                    group=platform_entities.Group(
                        id=event.group_id,
                        name=group_name,
                        permission=platform_entities.Permission.Member,
                    ),
                    special_title=special_title,
                ),
                message_chain=yiri_chain,
                time=event.time,
                source_platform_object=event,
            )
            return converted_event
        elif event.message_type == 'private':
            return platform_events.FriendMessage(
                sender=platform_entities.Friend(
                    id=event.sender['user_id'],
                    nickname=event.sender['nickname'],
                    remark='',
                ),
                message_chain=yiri_chain,
                time=event.time,
                source_platform_object=event,
            )


class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
    bot: aiocqhttp.CQHttp = pydantic.Field(exclude=True, default_factory=aiocqhttp.CQHttp)

    message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
    event_converter: AiocqhttpEventConverter = pydantic.Field(default_factory=AiocqhttpEventConverter)

    on_websocket_connection_event_cache: list[aiocqhttp.Event] = []
    _listener_wrappers: dict[
        tuple[typing.Type[platform_events.Event], typing.Callable],
        tuple[str, typing.Callable],
    ] = {}

    def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger):
        super().__init__(
            config=config,
            logger=logger,
        )

        async def shutdown_trigger_placeholder():
            while True:
                await asyncio.sleep(1)

        self.config['shutdown_trigger'] = shutdown_trigger_placeholder

        self.on_websocket_connection_event_cache = []
        self._listener_wrappers = {}

        if 'access-token' in config:
            self.bot = aiocqhttp.CQHttp(access_token=config['access-token'])
            del self.config['access-token']
        else:
            self.bot = aiocqhttp.CQHttp()

        self.bot.on_websocket_connection(self._on_websocket_connection)

    async def _on_websocket_connection(self, event: aiocqhttp.Event):
        for cached_event in self.on_websocket_connection_event_cache:
            if cached_event.self_id == event.self_id and cached_event.time == event.time:
                return

        self.on_websocket_connection_event_cache.append(event)
        if len(self.on_websocket_connection_event_cache) > 100:
            self.on_websocket_connection_event_cache.pop(0)
        await self.logger.info(f'WebSocket connection established, bot id: {event.self_id}')

    async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
        # Check if message contains a Forward component
        forward_msg = message.get_first(platform_message.Forward)
        if forward_msg:
            if target_type == 'group':
                # Send as merged forward message via OneBot API
                await self._send_forward_message(int(target_id), forward_msg)
                return
            else:
                await self.logger.warning(
                    f'Forward message is only supported for group targets, got target_type={target_type}. Falling through to normal send.'
                )

        aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]

        if target_type == 'group':
            await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg)
        elif target_type == 'person':
            await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg)

    async def _send_forward_message(self, group_id: int, forward: platform_message.Forward):
        """Send a merged forward message to a group using NapCat extended API."""
        messages = []

        for node in forward.node_list:
            # Build content for each node
            content = []
            if node.message_chain:
                for component in node.message_chain:
                    if isinstance(component, platform_message.Plain):
                        if component.text:
                            content.append({'type': 'text', 'data': {'text': component.text}})
                    elif isinstance(component, platform_message.Image):
                        img_data = {}
                        if component.base64:
                            b64 = _normalize_base64_payload(component.base64)
                            img_data['file'] = f'base64://{b64}'
                        elif component.url:
                            img_data['file'] = component.url
                        elif component.path:
                            img_data['file'] = str(component.path)

                        if img_data:
                            content.append({'type': 'image', 'data': img_data})

            if not content:
                continue

            # Build node data - use user_id and nickname format for NapCat
            user_id = str(node.sender_id) if node.sender_id else str(self.bot_account_id or '10000')
            node_data = {
                'type': 'node',
                'data': {
                    'user_id': user_id,
                    'nickname': node.sender_name or '未知',
                    'content': content,
                },
            }

            messages.append(node_data)

        if not messages:
            return

        # Build the full message payload for NapCat's send_forward_msg API
        # This matches the format used by GiveMeSetuPlugin
        bot_id = str(self.bot_account_id) if self.bot_account_id else '10000'
        payload = {
            'group_id': group_id,
            'user_id': bot_id,  # Required by NapCat for display
            'messages': messages,
        }

        # Add display settings if available
        if forward.display:
            if forward.display.title:
                payload['news'] = [{'text': forward.display.title}]
            if forward.display.brief:
                payload['prompt'] = forward.display.brief
            if forward.display.summary:
                payload['summary'] = forward.display.summary
            if forward.display.source:
                payload['source'] = forward.display.source

        try:
            # Use send_forward_msg (NapCat extended API) instead of send_group_forward_msg
            await self.logger.info(
                f'Sending forward message to group {group_id} with {len(messages)} nodes, payload keys: {list(payload.keys())}'
            )
            result = await self.bot.call_action('send_forward_msg', **payload)
            await self.logger.info(f'Forward message sent to group {group_id}, result: {result}')
        except Exception as e:
            await self.logger.error(f'Failed to send forward message to group {group_id}: {e}')
            # Fallback: try standard OneBot API with integer group_id
            try:
                await self.logger.info('Trying fallback API send_group_forward_msg')
                await self.bot.call_action('send_group_forward_msg', group_id=group_id, messages=messages)
                await self.logger.info(f'Forward message sent via fallback API to group {group_id}')
            except Exception as e2:
                await self.logger.error(f'Fallback also failed: {e2}')
                raise

    async def reply_message(
        self,
        message_source: platform_events.MessageEvent,
        message: platform_message.MessageChain,
        quote_origin: bool = False,
    ):
        aiocq_event = await AiocqhttpEventConverter.yiri2target(message_source, self.bot_account_id)
        aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
        if quote_origin:
            aiocq_msg = aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg

        return await self.bot.send(aiocq_event, aiocq_msg)

    async def is_muted(self, group_id: int) -> bool:
        return False

    def register_listener(
        self,
        event_type: typing.Type[platform_events.Event],
        callback: typing.Callable[
            [platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
        ],
    ):
        async def on_message(event: aiocqhttp.Event):
            self.bot_account_id = event.self_id
            try:
                return await callback(await self.event_converter.target2yiri(event, self.bot), self)
            except Exception:
                await self.logger.error(f'Error in on_message: {traceback.format_exc()}')
                traceback.print_exc()

        if event_type == platform_events.GroupMessage:
            self.bot.on_message('group')(on_message)
            self._listener_wrappers[(event_type, callback)] = ('message.group', on_message)
            # self.bot.on_notice()(on_message)
        elif event_type == platform_events.FriendMessage:
            self.bot.on_message('private')(on_message)
            self._listener_wrappers[(event_type, callback)] = ('message.private', on_message)
            # self.bot.on_notice()(on_message)
        # print(event_type)

    def unregister_listener(
        self,
        event_type: typing.Type[platform_events.Event],
        callback: typing.Callable[
            [platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
        ],
    ):
        listener = self._listener_wrappers.pop((event_type, callback), None)
        if listener is None:
            return

        event_name, wrapper = listener
        self.bot._bus.unsubscribe(event_name, wrapper)

    async def run_async(self):
        await self.bot._server_app.run_task(**self.config)

    async def kill(self) -> bool:
        # Current issue: existing connection will not be closed
        # self.should_shutdown = True
        self.on_websocket_connection_event_cache.clear()
        self.event_converter.clear()
        return False
