import asyncio

import discord
from redbot.core import Config, checks, commands
from redbot.core.commands import Cog


class ExclusiveRole(Cog):
    """
    Create roles that prevent all other roles from being added
    """

    def __init__(self, bot):
        super().__init__()
        self.bot = bot
        self.config = Config.get_conf(self, identifier=9999114111108101)
        default_guild = {"role_list": []}

        self.config.register_guild(**default_guild)

    @commands.guild_only()
    @commands.group(aliases=["exclusiverole"])
    async def exclusive(self, ctx):
        """Base command for managing exclusive roles"""

        if not ctx.invoked_subcommand:
            pass

    @exclusive.command(name="add")
    @checks.mod_or_permissions(administrator=True)
    async def exclusive_add(self, ctx, role: discord.Role):
        """Adds an exclusive role"""
        if role.id in (await self.config.guild(ctx.guild).role_list()):
            await ctx.send("That role is already exclusive")
            return

        async with self.config.guild(ctx.guild).role_list() as rl:
            rl.append(role.id)

        await self.check_guild(ctx.guild)

        await ctx.send("Exclusive role added")

    @exclusive.command(name="delete")
    @checks.mod_or_permissions(administrator=True)
    async def exclusive_delete(self, ctx, role: discord.Role):
        """Deletes an exclusive role"""
        if role.id not in (await self.config.guild(ctx.guild).role_list()):
            await ctx.send("That role is not exclusive")
            return

        async with self.config.guild(ctx.guild).role_list() as rl:
            rl.remove(role.id)

        await ctx.send("Exclusive role removed")

    @exclusive.command(name="list")
    @checks.mod_or_permissions(administrator=True)
    async def exclusive_list(self, ctx):
        """List current exclusive roles"""
        role_list = await self.config.guild(ctx.guild).role_list()
        guild: discord.Guild = ctx.guild

        role_list = [guild.get_role(role_id) for role_id in role_list]
        out = "**Exclusive roles**\n\n"

        for role in role_list:
            out += "{}\n".format(role)

        await ctx.send(out)

    async def check_guild(self, guild: discord.Guild):
        role_set = set(await self.config.guild(guild).role_list())
        for member in guild.members:
            try:
                await self.remove_non_exclusive_roles(member, role_set=role_set)
            except discord.Forbidden:
                pass

    async def remove_non_exclusive_roles(self, member: discord.Member, role_set=None):
        if role_set is None:
            role_set = set(await self.config.guild(member.guild).role_list())

        member_set = set([role.id for role in member.roles])
        to_remove = (member_set - role_set) - {member.guild.default_role.id}

        if to_remove and member_set & role_set:
            to_remove = [discord.utils.get(member.guild.roles, id=id) for id in to_remove]
            await member.remove_roles(*to_remove, reason="Exclusive roles")

    @commands.Cog.listener()
    async def on_member_update(self, before: discord.Member, after: discord.Member):
        if before.roles == after.roles:
            return

        await asyncio.sleep(1)

        role_set = set(await self.config.guild(after.guild).role_list())
        member_set = set([role.id for role in after.roles])

        if role_set & member_set:
            try:
                await self.remove_non_exclusive_roles(after, role_set=role_set)
            except discord.Forbidden:
                pass