from datetime import timedelta

from nonebot.adapters.discord import Bot, is_not_unset, is_unset
from nonebot.adapters.discord.api.model import Channel as DiscordChannel
from nonebot.adapters.discord.api.model import GuildMember, Snowflake
from nonebot.adapters.discord.api.model import User as DiscordUser
from nonebot.adapters.discord.api.types import ChannelType as DiscordChannelType
from nonebot.adapters.discord.api.types import UNSET
from nonebot.adapters.discord.event import (
    ChannelCreateEvent,
    ChannelDeleteEvent,
    ChannelUpdateEvent,
    DirectMessageCreateEvent,
    DirectMessageDeleteBulkEvent,
    DirectMessageDeleteEvent,
    DirectMessageReactionAddEvent,
    DirectMessageReactionRemoveAllEvent,
    DirectMessageReactionRemoveEmojiEvent,
    DirectMessageReactionRemoveEvent,
    DirectMessageUpdateEvent,
    Event,
    GuildBanAddEvent,
    GuildBanRemoveEvent,
    GuildCreateEvent,
    GuildDeleteEvent,
    GuildMemberAddEvent,
    GuildMemberRemoveEvent,
    GuildMemberUpdateEvent,
    GuildMessageCreateEvent,
    GuildMessageDeleteBulkEvent,
    GuildMessageDeleteEvent,
    GuildMessageReactionAddEvent,
    GuildMessageReactionRemoveAllEvent,
    GuildMessageReactionRemoveEmojiEvent,
    GuildMessageReactionRemoveEvent,
    GuildMessageUpdateEvent,
    GuildUpdateEvent,
    InteractionCreateEvent,
)

from nonebot_plugin_uninfo.constraint import SupportAdapter, SupportScope
from nonebot_plugin_uninfo.fetch import BasicInfo
from nonebot_plugin_uninfo.fetch import InfoFetcher as BaseInfoFetcher
from nonebot_plugin_uninfo.model import Member, MuteInfo, Role, Scene, SceneType, User

CHANNEL_TYPE = {
    DiscordChannelType.GUILD_TEXT: SceneType.CHANNEL_TEXT,
    DiscordChannelType.GUILD_VOICE: SceneType.CHANNEL_VOICE,
    DiscordChannelType.GUILD_CATEGORY: SceneType.CHANNEL_CATEGORY,
    DiscordChannelType.DM: SceneType.PRIVATE,
    DiscordChannelType.GROUP_DM: SceneType.GROUP,
    DiscordChannelType.GUILD_STAGE_VOICE: SceneType.CHANNEL_VOICE,
    DiscordChannelType.GUILD_DIRECTORY: SceneType.CHANNEL_CATEGORY,
}

BASE_URL = "https://cdn.discordapp.com/"


def avatar_url(id: str, avatar: str):
    if not avatar:
        return None
    return f"{BASE_URL}avatars/{id}/{avatar}.png?size=1024"


async def _handle_roles(bot: Bot, guild_id: str, roles: list[Snowflake]):
    if not roles:
        return [Role("MEMBER", 1, "member")]
    res = []
    resp = await bot.get_guild_roles(guild_id=int(guild_id))
    for role in resp:
        if role.id not in roles:
            continue
        perm = int(role.permissions)
        if perm & (1 << 3) == (1 << 3):
            if perm & (1 << 5) == (1 << 5):
                res.append(("OWNER", 100, role.name))
            res.append(("ADMINISTRATOR", 10, role.name))
        if perm & (1 << 4) == (1 << 4):
            res.append(("CHANNEL_ADMINISTRATOR", 9, role.name))
        res.append((str(role.id), 1, role.name))
    if not res:
        return [Role("MEMBER", 1, "member")]
    return [Role(*r) for r in res]


class InfoFetcher(BaseInfoFetcher):
    def extract_user(self, data):
        return User(
            id=data["user_id"],
            name=data["name"],
            avatar=avatar_url(data["user_id"], data["avatar"]),
        )

    def extract_scene(self, data):
        if "guild_id" in data:
            if "channel_id" in data:
                return Scene(
                    id=data["channel_id"],
                    name=data.get("channel_name"),
                    type=data.get("channel_type", SceneType.CHANNEL_TEXT),
                    avatar=avatar_url(data["channel_id"], data.get("channel_avatar") or ""),
                    parent=Scene(
                        id=data["guild_id"],
                        name=data.get("guild_name"),
                        type=SceneType.GUILD,
                        avatar=avatar_url(data["guild_id"], data.get("guild_avatar") or ""),
                    ),
                )
            return Scene(
                id=data["guild_id"],
                name=data.get("guild_name"),
                type=SceneType.GUILD,
                avatar=avatar_url(data["guild_id"], data.get("guild_avatar") or ""),
            )
        if "channel_id" in data:
            return Scene(
                id=data["channel_id"],
                name=data.get("channel_name"),
                type=data.get("channel_type", SceneType.CHANNEL_TEXT),
                avatar=avatar_url(data["channel_id"], data.get("channel_avatar") or ""),
            )
        return Scene(
            id=data["user_id"],
            type=SceneType.PRIVATE,
            name=data["name"],
            avatar=avatar_url(data["user_id"], data.get("avatar") or ""),
        )

    def extract_member(self, data, user: User | None):
        if "guild_id" in data or "channel_id" in data:
            if user:
                return Member(user, nick=data["nickname"], roles=data.get("roles", []), joined_at=data.get("joined_at"))
            return Member(
                User(
                    id=data["user_id"],
                    name=data["name"],
                    avatar=avatar_url(data["user_id"], data.get("avatar") or ""),
                ),
                nick=data["nickname"],
                roles=data.get("roles", []),
                joined_at=data.get("joined_at"),
            )
        return None

    async def query_user(self, bot: Bot, user_id: str):
        if bot.self_id == user_id:
            user = await bot.get_current_user()
        else:
            user = await bot.get_user(user_id=int(user_id))
        data = {
            "user_id": str(user.id),
            "name": user.username,
            "avatar": user.avatar,
        }
        return self.extract_user(data)

    async def query_scene(self, bot: Bot, scene_type: SceneType, scene_id: str, *, parent_scene_id: str | None = None):
        if scene_type == SceneType.PRIVATE:
            user = await bot.get_user(user_id=int(scene_id))
            return self.extract_scene(
                {
                    "user_id": user.id,
                    "name": user.username,
                    "avatar": user.avatar,
                }
            )

        elif scene_type == SceneType.GUILD:
            guild = await bot.get_guild(guild_id=int(scene_id))
            return self.extract_scene(
                {
                    "guild_id": str(guild.id),
                    "guild_name": guild.name,
                    "guild_avatar": guild.icon,
                }
            )

        elif scene_type >= SceneType.CHANNEL_TEXT:
            channel = await bot.get_channel(channel_id=int(scene_id))
            return self.extract_scene(
                {
                    "channel_id": str(channel.id),
                    "channel_name": channel.name,
                    "channel_type": CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
                    "channel_avatar": channel.icon,
                }
            )

    async def query_member(self, bot: Bot, scene_type: SceneType, parent_scene_id: str, user_id: str):
        guild_id = parent_scene_id

        member = await bot.get_guild_member(guild_id=int(guild_id), user_id=int(user_id))
        if isinstance(member.user, DiscordUser):
            user = User(
                id=str(member.user.id),
                name=member.user.username,
                avatar=member.user.avatar,
            )
            return Member(
                user=user,
                nick="" if member.nick is UNSET else member.nick,
                roles=await _handle_roles(bot, guild_id, member.roles),
                joined_at=member.joined_at,
                mute=None if member.mute is UNSET else MuteInfo(muted=member.mute, duration=timedelta(60)),
            )

    def query_users(self, bot: Bot):
        raise NotImplementedError

    async def query_scenes(self, bot: Bot, scene_type: SceneType | None = None, *, parent_scene_id: str | None = None):
        if scene_type in (SceneType.PRIVATE, SceneType.GROUP):
            return

        guilds = await bot.get_current_user_guilds(limit=100)
        while guilds:
            for guild in guilds:
                if parent_scene_id is None or str(guild.id) == parent_scene_id:
                    _guild = Scene(
                        id=str(guild.id),
                        type=SceneType.GUILD,
                        name=guild.name,
                        avatar=avatar_url(str(guild.id), guild.icon or ""),
                    )
                    if scene_type is None or scene_type == SceneType.GUILD:
                        yield _guild
                    if scene_type == SceneType.GUILD:
                        continue
                    channels = await bot.get_guild_channels(guild_id=guild.id)
                    for channel in channels:
                        yield Scene(
                            id=str(channel.id),
                            type=CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
                            name=(channel.name if is_not_unset(channel.name) else None),
                            avatar=(
                                avatar_url(str(channel.id), channel.icon)
                                if is_not_unset(channel.icon) and channel.icon is not None
                                else None
                            ),
                            parent=_guild,
                        )
            if len(guilds) < 100:
                break
            guilds = await bot.get_current_user_guilds(limit=100, after=guilds[-1].id)

    async def query_members(self, bot: Bot, scene_type: SceneType, parent_scene_id: str):
        guild_id = parent_scene_id

        members = await bot.list_guild_members(guild_id=int(guild_id), limit=100)
        while members:
            for member in members:
                if isinstance(member.user, DiscordUser):
                    user = User(
                        id=str(member.user.id),
                        name=member.user.username,
                        avatar=member.user.avatar,
                    )
                else:
                    continue
                yield Member(
                    user=user,
                    nick="" if member.nick is UNSET else member.nick,
                    roles=await _handle_roles(bot, guild_id, member.roles),
                    joined_at=member.joined_at,
                    mute=None if member.mute is UNSET else MuteInfo(muted=member.mute, duration=timedelta(60)),
                )
            if len(members) < 100:
                break
            if is_unset(members[-1].user):
                raise ValueError("Discord member payload is missing user")
            members = await bot.list_guild_members(guild_id=int(guild_id), limit=100, after=members[-1].user.id)

    def supply_self(self, bot) -> BasicInfo:
        return {
            "self_id": str(bot.self_id),
            "adapter": SupportAdapter.discord,
            "scope": SupportScope.discord,
        }


fetcher = InfoFetcher(SupportAdapter.discord)


@fetcher.supply
async def _(bot: Bot, event: InteractionCreateEvent):
    if isinstance(event.user, DiscordUser):
        base = {
            "user_id": str(event.user.id),
            "name": event.user.username,
            "nickname": "",
            "avatar": event.user.avatar,
        }
        return base
    if is_unset(event.member):
        raise ValueError("Discord interaction payload is missing member")
    if is_unset(event.guild_id):
        raise ValueError("Discord interaction payload is missing guild_id")
    if is_unset(event.channel_id):
        raise ValueError("Discord interaction payload is missing channel_id")
    if is_unset(event.member.user):
        raise ValueError("Discord interaction member payload is missing user")
    guild_info = await bot.get_guild(guild_id=event.guild_id)
    base = {
        "user_id": str(event.member.user.id),
        "name": event.member.user.username,
        "nickname": event.member.nick,
        "avatar": event.member.user.avatar,
        "guild_id": str(event.guild_id),
        "guild_name": guild_info.name,
        "guild_avatar": guild_info.icon,
    }
    if isinstance(event.channel, DiscordChannel):
        base |= {
            "channel_id": str(event.channel.id),
            "channel_name": event.channel.name or "",
            "channel_type": CHANNEL_TYPE.get(event.channel.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": event.channel.icon or "",
        }
    else:
        channel = await bot.get_channel(channel_id=event.channel_id)
        base |= {
            "channel_id": str(channel.id),
            "channel_name": channel.name or "",
            "channel_type": CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": channel.icon or "",
        }
    return base


@fetcher.supply
async def _(
    bot: Bot,
    event: DirectMessageCreateEvent | GuildMessageCreateEvent | DirectMessageUpdateEvent | GuildMessageUpdateEvent,
):
    if is_unset(event.author):
        raise ValueError("Discord message payload is missing author")
    base = {
        "user_id": str(event.author.id),
        "name": event.author.username,
        "nickname": event.author.username,
        "avatar": event.author.avatar,
    }
    if isinstance(event.guild_id, Snowflake):
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        channel = await bot.get_channel(channel_id=int(event.channel_id))
        base |= {
            "channel_id": str(event.channel_id),
            "channel_name": channel.name,
            "channel_type": CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": channel.icon,
        }
        if isinstance(event.member, GuildMember):
            base |= {
                "nickname": event.member.nick or event.author.username,
                "roles": await _handle_roles(bot, str(event.guild_id), event.member.roles),
                "joined_at": event.member.joined_at,
            }
        else:
            member = await bot.get_guild_member(guild_id=event.guild_id, user_id=event.author.id)
            base |= {
                "nickname": member.nick or event.author.username,
                "roles": await _handle_roles(bot, str(event.guild_id), member.roles),
                "joined_at": member.joined_at,
            }
    return base


@fetcher.supply
async def _(
    bot: Bot,
    event: (
        DirectMessageDeleteEvent
        | DirectMessageDeleteBulkEvent
        | GuildMessageDeleteEvent
        | GuildMessageDeleteBulkEvent
        | GuildMessageReactionRemoveAllEvent
        | DirectMessageReactionRemoveAllEvent
        | DirectMessageReactionRemoveEmojiEvent
        | GuildMessageReactionRemoveEmojiEvent
    ),
):
    self_info = await bot.get_current_user()
    base = {
        "user_id": str(self_info.id),
        "name": self_info.username,
        "nickname": self_info.username,
        "avatar": self_info.avatar,
    }
    if isinstance(event.guild_id, Snowflake):
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        channel = await bot.get_channel(channel_id=int(event.channel_id))
        base |= {
            "channel_id": str(event.channel_id),
            "channel_name": channel.name,
            "channel_type": CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": channel.icon,
        }
    return base


@fetcher.supply
async def _(
    bot: Bot,
    event: (
        DirectMessageReactionAddEvent
        | DirectMessageReactionRemoveEvent
        | GuildMessageReactionAddEvent
        | GuildMessageReactionRemoveEvent
    ),
):
    user = await bot.get_user(user_id=event.user_id)
    base = {
        "user_id": str(event.user_id),
        "name": user.username,
        "nickname": user.username,
        "avatar": user.avatar,
    }
    if isinstance(event.guild_id, Snowflake):
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        channel = await bot.get_channel(channel_id=int(event.channel_id))
        base |= {
            "channel_id": str(event.channel_id),
            "channel_name": channel.name,
            "channel_type": CHANNEL_TYPE.get(channel.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": channel.icon,
        }
        member = await bot.get_guild_member(guild_id=event.guild_id, user_id=event.user_id)
        base |= {
            "nickname": member.nick or user.username,
            "roles": await _handle_roles(bot, str(event.guild_id), member.roles),
            "joined_at": member.joined_at,
        }
    return base


@fetcher.supply_wildcard
async def _(bot: Bot, event: Event):
    if isinstance(event, (ChannelCreateEvent, ChannelDeleteEvent, ChannelUpdateEvent)):
        self_info = await bot.get_current_user()
        base = {
            "user_id": str(self_info.id),
            "name": self_info.username,
            "nickname": self_info.username,
            "avatar": self_info.avatar,
        }
        base |= {
            "channel_id": str(event.id),
            "channel_name": event.name,
            "channel_type": CHANNEL_TYPE.get(event.type, SceneType.CHANNEL_TEXT),
            "channel_avatar": event.icon,
        }
        if isinstance(event.guild_id, Snowflake):
            guild = await bot.get_guild(guild_id=int(event.guild_id))
            base |= {
                "guild_id": str(event.guild_id),
                "guild_name": guild.name,
                "guild_avatar": guild.icon,
            }
        return base
    if isinstance(event, (GuildBanAddEvent, GuildBanRemoveEvent)):
        base = {
            "user_id": str(event.user.id),
            "name": event.user.username,
            "nickname": event.user.username,
            "avatar": event.user.avatar,
        }
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        return base
    if isinstance(event, (GuildCreateEvent, GuildUpdateEvent)):
        self_info = await bot.get_current_user()
        base = {
            "user_id": str(self_info.id),
            "name": self_info.username,
            "nickname": self_info.username,
            "avatar": self_info.avatar,
        }
        base |= {
            "guild_id": str(event.id),
            "guild_name": event.name,
            "guild_avatar": event.icon,
        }
        return base
    if isinstance(event, GuildDeleteEvent):
        self_info = await bot.get_current_user()
        base = {
            "user_id": str(self_info.id),
            "name": self_info.username,
            "nickname": self_info.username,
            "avatar": self_info.avatar,
            "guild_id": str(event.id),
            "guild_name": "",
            "guild_avatar": "",
        }
        return base
    if isinstance(event, GuildMemberAddEvent):
        if is_unset(event.user):
            raise ValueError("Discord guild member add payload is missing user")
        base = {
            "user_id": str(event.user.id),
            "name": event.user.username,
            "nickname": event.nick or "",
            "avatar": event.avatar or event.user.avatar,
            "roles": await _handle_roles(bot, str(event.guild_id), event.roles),
            "joined_at": event.joined_at,
        }
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        return base
    if isinstance(event, GuildMemberUpdateEvent):
        base = {
            "user_id": str(event.user.id),
            "name": event.user.username,
            "nickname": event.nick or "",
            "avatar": event.user.avatar,
            "roles": await _handle_roles(bot, str(event.guild_id), event.roles),
            "joined_at": event.joined_at,
        }
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        return base
    if isinstance(event, GuildMemberRemoveEvent):
        base = {
            "user_id": str(event.user.id),
            "name": event.user.username,
            "nickname": event.user.username,
            "avatar": event.user.avatar,
        }
        guild = await bot.get_guild(guild_id=int(event.guild_id))
        base |= {
            "guild_id": str(event.guild_id),
            "guild_name": guild.name,
            "guild_avatar": guild.icon,
        }
        return base
    raise NotImplementedError
