from __future__ import annotations

import random
from typing import TYPE_CHECKING

from graia.amnesia.builtins.memcache import Memcache, MemcacheService
from graia.amnesia.message import MessageChain
from loguru import logger

from avilla.core import Context
from avilla.core.ryanvk.collector.account import AccountCollector
from avilla.core.selector import Selector
from avilla.red.capability import RedCapability
from avilla.standard.core.message import (
    MessageReceived,
    MessageRevoke,
    MessageSend,
    MessageSent,
)
from avilla.standard.qq.elements import Forward, Node

if TYPE_CHECKING:
    from avilla.red.account import RedAccount  # noqa
    from avilla.red.protocol import RedProtocol  # noqa


class RedMessageActionPerform((m := AccountCollector["RedProtocol", "RedAccount"]())._):
    m.namespace = "avilla.protocol/red::action"
    m.identify = "message"

    async def handle_reply(self, target: Selector):
        cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache
        reply_msg = await cache.get(
            f"red/account({self.account.route['account']}).message({target.pattern['message']})"
        )
        if reply_msg:
            return {
                "elementType": 7,
                "replyElement": {
                    "replayMsgId": reply_msg["msgId"],
                    "replayMsgSeq": reply_msg["msgSeq"],
                    "senderUin": reply_msg["senderUin"],
                    "senderUinStr": str(reply_msg["senderUin"]),
                },
            }
        logger.warning(f"Unknown message {target.pattern['message']} for reply")
        return None

    @m.entity(MessageSend.send, target="land.group")
    async def send_group_msg(
        self,
        target: Selector,
        message: MessageChain,
        *,
        reply: Selector | None = None,
    ) -> Selector:
        if message.has(Forward):
            return await self.send_group_forward_msg(target, message.get_first(Forward))
        msg = await RedCapability(self.account.staff).serialize(message)
        if reply and (reply_msg := await self.handle_reply(reply)):
            msg.insert(0, reply_msg)
        resp = await self.account.websocket_client.call_http(
            "post",
            "api/message/send",
            {
                "peer": {
                    "chatType": 2,
                    "peerUin": target.pattern["group"],
                    "guildId": None,
                },
                "elements": msg,
            },
        )
        if "msgId" in resp:
            msg_id = resp["msgId"]
            event = await RedCapability(
                self.account.staff.ext({"connection": self.account.websocket_client})
            ).event_callback("message::recv", resp)
            if TYPE_CHECKING:
                assert isinstance(event, MessageReceived)
            event.context = self.account.get_context(target.member(self.account.route["account"]))
            event.message.scene = target
            event.message.sender = target.member(self.account.route["account"])
            self.protocol.post_event(MessageSent(event.context, event.message, self.account))
        else:
            msg_id = "unknown"
        return Selector().land(self.account.route["land"]).group(target.pattern["group"]).message(msg_id)

    @m.entity(MessageSend.send, target="land.friend")
    async def send_friend_msg(
        self,
        target: Selector,
        message: MessageChain,
        *,
        reply: Selector | None = None,
    ) -> Selector:
        msg = await RedCapability(self.account.staff).serialize(message)
        if reply and (reply_msg := await self.handle_reply(reply)):
            msg.insert(0, reply_msg)
        resp = await self.account.websocket_client.call_http(
            "post",
            "api/message/send",
            {
                "peer": {
                    "chatType": 1,
                    "peerUin": target.pattern["friend"],
                    "guildId": None,
                },
                "elements": msg,
            },
        )
        if "msgId" in resp:
            msg_id = resp["msgId"]
            event = await RedCapability(
                self.account.staff.ext({"connection": self.account.websocket_client})
            ).event_callback("message::recv", resp)
            if TYPE_CHECKING:
                assert isinstance(event, MessageReceived)
            event.context = Context(
                self.account,
                self.account.route,
                target,
                target,
                self.account.route,
            )
            event.message.scene = target
            event.message.sender = self.account.route
            self.protocol.post_event(MessageSent(event.context, event.message, self.account))
        else:
            msg_id = "unknown"
        return Selector().land(self.account.route["land"]).friend(target.pattern["friend"]).message(msg_id)

    @m.entity(MessageRevoke.revoke, target="land.group.message")
    async def revoke_group_msg(
        self,
        target: Selector,
    ) -> None:
        await self.account.websocket_client.call_http(
            "post",
            "api/message/recall",
            {
                "peer": {
                    "chatType": 2,
                    "peerUid": target.pattern["group"],
                    "guildId": None,
                },
                "msgId": [target.pattern["message"]],
            },
        )

    @m.entity(MessageRevoke.revoke, target="land.friend.message")
    async def revoke_friend_msg(
        self,
        target: Selector,
    ) -> None:
        await self.account.websocket_client.call_http(
            "post",
            "api/message/recall",
            {
                "peer": {
                    "chatType": 1,
                    "peerUid": target.pattern["friend"],
                    "guildId": None,
                },
                "msgId": [target.pattern["message"]],
            },
        )

    @m.entity(RedCapability.send_forward, target="land.group")
    async def send_group_forward_msg(self, target: Selector, forward: Forward) -> Selector:
        if all(node.mid for node in forward.nodes):
            await self.account.websocket_client.call_http(
                "post",
                "api/message/unsafeSendForward",
                {
                    "dstContact": {
                        "chatType": 2,
                        "peerUid": target.pattern["group"],
                        "guildId": None,
                    },
                    "srcContact": {
                        "chatType": 2,
                        "peerUid": target.pattern["group"],
                        "guildId": None,
                    },
                    "msgInfos": [
                        {"msgId": node.mid["message"], "senderShowName": node.name}  # type: ignore
                        for node in forward.nodes
                    ],
                },
            )
            return Selector().land(self.account.route["land"]).group(target.pattern["group"]).message("unknown")
        if all(node.content for node in forward.nodes):
            elems = []
            base_seq = random.randint(0, 65535)
            for node in forward.nodes:
                elems.append(await self.export_forward_node(base_seq, node, target))
                base_seq += 1
            await self.account.websocket_client.call_http(
                "post",
                "api/message/unsafeSendForward",
                {
                    "dstContact": {
                        "chatType": 2,
                        "peerUid": target.pattern["group"],
                        "guildId": None,
                    },
                    "srcContact": {
                        "chatType": 2,
                        "peerUid": target.pattern["group"],
                        "guildId": None,
                    },
                    "msgElements": elems,
                },
            )
            return Selector().land(self.account.route["land"]).group(target.pattern["group"]).message("unknown")
        raise ValueError("Forward message must have at least one node with content or mid")

    async def export_forward_node(self, seq: int, node: Node, target: Selector):
        cap = RedCapability(self.account.staff)
        elems = [await cap.forward_export(elem) for elem in node.content]  # type: ignore
        return {
            "head": {
                "field2": node.uid,
                "field8": {
                    "field1": target.pattern["group"],
                    "field4": node.name,
                },
            },
            "content": {
                "field1": 82,
                "field4": random.randint(0, 4294967295),
                "field5": seq,
                "field6": int(node.time.timestamp()),
                "field7": 1,
                "field8": 0,
                "field9": 0,
                "field15": {"field1": 0, "field2": 0},
            },
            "body": {"richText": {"elems": elems}},
        }
