import nonebot
from nonebot.matcher import Matcher
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageSegment
from nonebot.adapters.onebot.v11.message import Message
from nonebot.adapters.onebot.v11.event import GroupRecallNoticeEvent
from nonebot.params import CommandArg
from discord import app_commands
import threading
import traceback
import asyncio
import discord
import httpx
import copy
import json
import time
import os

from .config import *
from .utils import local as uLocal
from .utils import send as uSend
from .utils import download as uDownload

os.environ["HTTP_PROXY"] = HTTP_PROXY
os.environ["HTTPS_PROXY"] = HTTP_PROXY
already_start_discord_bot = False
discord_bot_started = False
intents = discord.Intents.default()
intents.message_content = True
bot_restart_time = 0
dcclient = discord.Client(intents=intents, proxy=HTTP_PROXY)
if not os.path.exists(qq_bind_file):
    json.dump({}, open(qq_bind_file, "w"))
if not os.path.exists(qq_bind_file_2):
    json.dump({}, open(qq_bind_file_2, "w"))
temp_bind_qq = {}  # qq:rid
temp_bind_dis = {}  # rid:dis
temp_message_ids = []  # (qq:dc)
ENABLE_LOG = False
qq_commad_name = QQ_COMMAND_PREFIX + QQ_COMMAND


def log(msg, level="INFO"):
    if ENABLE_LOG:
        with open(LOG_FILE, "a") as f:
            f.write(
                f"[{time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())}] [{level}] {msg}\n"
            )


def get_otherside_message_id(_id, this):
    if this == "qq":
        for i in temp_message_ids:
            if i[0] == str(_id):
                return i[1]
    elif this == "dc":
        for i in temp_message_ids:
            if i[1] == str(_id):
                return i[0]
    return None


def add_temp_message_id(qq_id, dc_id):
    temp_message_ids.append((str(qq_id), str(dc_id)))
    if len(temp_message_ids) > 1000:
        temp_message_ids.pop(0)


def set_qq_bind(discord_id, qq_id):
    qq_bind = json.load(open(qq_bind_file, "r"))
    qq_bind[str(discord_id)] = str(qq_id)
    qq_bind_2 = json.load(open(qq_bind_file_2, "r"))
    qq_bind_2[str(qq_id)] = str(discord_id)
    json.dump(qq_bind, open(qq_bind_file, "w"))
    json.dump(qq_bind_2, open(qq_bind_file_2, "w"))


async def send_list_message(content_list):
    for i in content_list:
        if not i[1]:
            await uSend.send_message(i[0], CHANNEL_ID)
        else:
            await gbot.send_group_msg(group_id=QQ_ID, message=i[0])


def process_xdbot_command(discord_id, command):
    discord_id = str(discord_id)
    global temp_bind_qq, temp_bind_dis
    command = command.replace(DISCORD_COMMAND_PREFIX, "", 1)
    if command.startswith("bind"):
        try:
            qq_id = command.split(" ")[1]
            if qq_id in temp_bind_qq:
                if temp_bind_dis[temp_bind_qq[qq_id]] == discord_id:
                    return [
                        (
                            f"<@{discord_id}> 你已在请求绑定QQ号,请在QQ群内发送 `{qq_commad_name} bind {temp_bind_qq[qq_id]}` 进行绑定",
                            False,
                        ),
                        (
                            f"[CQ:at,qq={qq_id}] 请发送「{qq_commad_name} bind {temp_bind_qq[qq_id]}」绑定 Discord({discord_id}) 如果这不是你的操作, 请忽略本条消息",
                            True,
                        ),
                    ]
                else:
                    del temp_bind_qq[qq_id]
                    del temp_bind_dis[temp_bind_qq[qq_id]]
            rid = uLocal.genRandomID(24)
            temp_bind_qq[qq_id] = rid
            temp_bind_dis[rid] = discord_id
            return [
                (f"<@{discord_id}> 请在QQ群内发送 `{qq_commad_name} bind {rid}` 进行绑定", False),
                (
                    f"[CQ:at,qq={qq_id}] 请发送「{qq_commad_name} bind {rid}」绑定 Discord({discord_id}) 如果这不是你的操作, 请忽略本条消息",
                    True,
                ),
            ]
        except:
            if qq_id := uLocal.get_qq_bind(discord_id):
                return [(f"<@{discord_id}> 你已绑定QQ号 `{qq_id}`", False)]
            return [
                (
                    f"<@{discord_id}> 命令格式错误,应为: `{DISCORD_COMMAND_PREFIX}bind <uid>`",
                    False,
                )
            ]
    if not (qq_id := uLocal.get_qq_bind(discord_id)):
        return [
            (f"<@{discord_id}> 请先使用 `{DISCORD_COMMAND_PREFIX}bind <uid>` 绑定QQ号", False)
        ]
    if not QQ_SUDO_FORMAT:
        return [
            (
                f"<@{discord_id}> 您已绑定过QQ号 可以使用 `{DISCORD_COMMAND_PREFIX}bind <uid>` 修改绑定",
                False,
            )
        ]
    return [(f"{QQ_SUDO_FORMAT.format(Q=qq_id, C=command)}", True)]


def startDiscordBot():
    global bot_restart_time
    global dcclient
    time_1 = time.time()
    if bot_restart_time > 3:
        asyncio.run(
            gbot.send_group_msg(
                group_id=QQ_ID, message=PREFIX + "转发 Bot 短时间启动失败次数过多,已停止"
            )
        )
        return
    if bot_restart_time != 0:
        asyncio.run(
            gbot.send_group_msg(
                group_id=QQ_ID, message=PREFIX + f"转发 Bot 正在重启 ({bot_restart_time}次)"
            )
        )
    try:
        dcclient = discord.Client(intents=intents, proxy=HTTP_PROXY)

        @dcclient.event
        async def on_message(message):
            if message.author.id == dcclient.user.id or str(message.webhook_id) == str(
                WEBHOOK_ID
            ):
                return
            log(
                f"Received message from Discord: Message={message.content} UserID={message.author.id} ChannelID={message.channel.id} MessageID={message.id}"
                f" ReplyMessageID={message.reference.message_id if message.reference else None}"
            )
            if message.content.startswith(DISCORD_COMMAND_PREFIX):
                await send_list_message(
                    process_xdbot_command(message.author.id, message.content)
                )
                return
            ms = Message(
                uLocal.replace_ids_with_cq_at(
                    PREFIX + f"<{message.author.name}> {message.content}"
                )
            )
            if message.channel.id == CHANNEL_ID:
                try:
                    async with message.channel.typing():
                        if message.attachments:
                            for atta in message.attachments:
                                ms += MessageSegment.image(
                                    await uDownload.download_image(atta.url)
                                )
                        if message.reference:
                            if reply_to_qq_id := get_otherside_message_id(
                                message.reference.message_id, "dc"
                            ):
                                ms = MessageSegment.reply(int(reply_to_qq_id)) + ms
                        msg_id = (
                            await gbot.send_group_msg(group_id=QQ_ID, message=ms)
                        )["message_id"]
                        add_temp_message_id(msg_id, message.id)
                except:
                    await message.add_reaction(
                        discord.utils.get(message.guild.emojis, name=QQ_FORWARD_FAILED)
                    )
                    await message.reply("```" + traceback.format_exc() + "```")

        @dcclient.event
        async def on_message_edit(before, after):
            if before.author.id == dcclient.user.id or str(before.webhook_id) == str(
                WEBHOOK_ID
            ):
                return
            if before.channel.id == CHANNEL_ID:
                async with before.channel.typing():
                    if qq_id := get_otherside_message_id(before.id, "dc"):
                        ms = (
                            MessageSegment.reply(int(qq_id)) + "[编辑消息] " + after.content
                        )
                        msg_id = (
                            await gbot.send_group_msg(group_id=QQ_ID, message=ms)
                        )["message_id"]
                        add_temp_message_id(msg_id, before.id)

        @dcclient.event
        async def on_message_delete(message):
            if message.author.id == dcclient.user.id or str(message.webhook_id) == str(
                WEBHOOK_ID
            ):
                return
            if message.channel.id == CHANNEL_ID:
                async with message.channel.typing():
                    if qq_id := get_otherside_message_id(message.id, "dc"):
                        ms = MessageSegment.reply(int(qq_id)) + "[消息已被删除]"
                        msg_id = (
                            await gbot.send_group_msg(group_id=QQ_ID, message=ms)
                        )["message_id"]
                        add_temp_message_id(msg_id, message.id)

        dctree = app_commands.CommandTree(dcclient)

        @dctree.command(name="debug-dmb", description="Get temp_message_ids")
        async def debug_dmb(interaction):
            log("Value of temp_message_ids: " + str(temp_message_ids))
            await interaction.response.send_message(f"Successfully write to log file")

        @dcclient.event
        async def on_ready():
            global discord_bot_started
            await dctree.sync(guild=discord.Object(id=GUILD_ID))
            discord_bot_started = True
            log("Discord Bot started")

        dcclient.run(TOKEN)
    except:
        del dcclient
        time_2 = time.time()
        asyncio.run(
            gbot.send_group_msg(
                group_id=QQ_ID,
                message=PREFIX
                + f"转发 Bot 异常断开连接 (已运行{int(time_2-time_1)}秒)\n"
                + traceback.format_exc().split("\n")[-2],
            )
        )
        if time_2 - time_1 <= 90:
            bot_restart_time += 1
            startDiscordBot()
        else:
            bot_restart_time = 1
            startDiscordBot()


@nonebot.on_message().handle()
async def _(matcher: Matcher, bot: Bot, event: GroupMessageEvent):
    try:
        if event.group_id == QQ_ID:
            channel = CHANNEL_ID
        else:
            return
    except:
        return
    log(
        f"Received message from QQ: Message={str(event.get_message())} UserID={event.get_user_id()} GroupID={event.group_id} MessageID={event.message_id} "
        f"ReplyMessageID={event.reply.message_id if event.reply else None} SenderNickname={event.sender.nickname}"
    )
    msg = uLocal.replace_cq_at_with_ids(uLocal.process_text(str(event.get_message())))
    if msg.startswith(DISCORD_COMMAND_PREFIX * 2):
        await uSend.send_message(msg[2:], channel)
        return
    uid = event.get_user_id()
    msg_nocq = copy.deepcopy(msg)
    images = uLocal.get_url(msg)
    for i in uLocal.get_cq_images(msg):
        msg_nocq = msg_nocq.replace(i, " [图片] ")
    if event.reply:
        if reply_to_dc_id := get_otherside_message_id(event.reply.message_id, "qq"):
            msg_content = (
                (
                    await uDownload.get_discord_message_content(
                        reply_to_dc_id, removereply=True
                    )
                )
                .strip()
                .replace("\n", " ")
            )
            msg_nocq = (
                f"> {uLocal.generate_message_link(reply_to_dc_id)}\n> *{msg_content}*\n"
                + msg_nocq
            )
    msgid = await uSend.webhook_send_message(
        event.sender.nickname + " [QQ]", uLocal.get_avatar_url(uid), msg_nocq, images
    )
    add_temp_message_id(event.message_id, msgid)


@nonebot.on_notice().handle()
async def _(matcher: Matcher, bot: Bot, event: GroupRecallNoticeEvent):
    try:
        if event.group_id == QQ_ID:
            channel = CHANNEL_ID
        else:
            return
    except:
        return
    if dc_id := get_otherside_message_id(event.message_id, "qq"):
        async with httpx.AsyncClient() as client:
            await client.patch(
                url=WEBHOOK_URL + "/messages/" + dc_id,
                headers={"Content-Type": "application/json"},
                json={
                    "content": "||"
                    + (await uDownload.get_discord_message_content(dc_id, False))
                    + "||"
                },
            )


@nonebot.on_message().handle()
async def _(matcher: Matcher, bot: Bot):
    global already_start_discord_bot
    if not already_start_discord_bot:
        global gbot
        gbot = bot
        already_start_discord_bot = True
        threading.Thread(target=startDiscordBot).start()
        log("Discord Bot thread started")
        matcher.destroy()


@nonebot.on_command(QQ_COMMAND).handle()
async def _(
    matcher: Matcher, bot: Bot, event: GroupMessageEvent, args: Message = CommandArg()
):
    global temp_bind_qq, temp_bind_dis, bot_restart_time, dcclient
    try:
        if event.group_id == QQ_ID:
            channel = CHANNEL_ID
        else:
            return
    except:
        return
    args = args.extract_plain_text().strip().split(" ")
    if args[0] == "bind":
        try:
            token = args[1]
        except:
            if dis_id := uLocal.get_qq_bind_discord(event.get_user_id()):
                await matcher.finish(f"你已绑定 Discord({dis_id})", at_sender=True)
            await matcher.finish(
                f"你还未绑定 Discord, 请到消息转发频道下发送「{DISCORD_COMMAND_PREFIX}bind <uid>」进行绑定",
                at_sender=True,
            )
        if token in temp_bind_dis and temp_bind_qq[event.get_user_id()] == token:
            set_qq_bind(temp_bind_dis[token], event.get_user_id())
            await uSend.send_message(
                f"<@{temp_bind_dis[token]}> QQ `{event.get_user_id()}` 绑定成功", channel
            )
            del temp_bind_qq[event.get_user_id()]
            del temp_bind_dis[token]
            await matcher.finish(f"Discord 绑定成功", at_sender=True)
        else:
            await matcher.finish("绑定 token 无效", at_sender=True)
    elif args[0] == "restart":
        if bot_restart_time >= 3:
            bot_restart_time = 0
            threading.Thread(target=startDiscordBot).start()
            await matcher.finish("正在尝试重启转发 Bot", at_sender=True)
        else:
            await matcher.finish("转发 Bot 正常运行中", at_sender=True)
    elif args[0] == "log":
        global ENABLE_LOG
        ENABLE_LOG = not ENABLE_LOG
        await matcher.finish(f"日志记录状态: {ENABLE_LOG}", at_sender=True)
    elif args[0] == "debug":
        log("Value of temp_message_ids: " + str(temp_message_ids))
        await matcher.finish("Successfully write to log file", at_sender=True)
    await matcher.finish(
        BOT_NAME
        + " 命令帮助\n"
        + qq_commad_name
        + " bind <token> - 绑定 Discord 账户\n"
        + qq_commad_name
        + " restart - 手动重启转发 Bot (仅在自动重启失败后可用)\n"
        + qq_commad_name
        + " log - 开启/关闭日志记录\n"
        + qq_commad_name
        + " debug - 在日志中获取 temp_message_ids",
        at_sender=True,
    )
