"""General, basic commands that are common for Discord bots"""

import inspect
import discord
from discord.ext.commands import BadArgument, cooldown, BucketType, Group, has_permissions

from ._utils import *
from .. import db


class General(Cog):
    """General commands common to all Discord bots."""

    @command()
    async def ping(self, ctx):
        """Check the bot is online, and calculate its response time."""
        if ctx.guild is None:
            location = 'DMs'
        else:
            location = 'the **%s** server' % ctx.guild.name
        response = await ctx.send('Pong! We\'re in %s.' % location)
        delay = response.created_at - ctx.message.created_at
        await response.edit(
            content=response.content + '\nTook %d ms to respond.' % (delay.seconds * 1000 + delay.microseconds // 1000))

    ping.example_usage = """
    `{prefix}ping` - Calculate and display the bot's response time
    """

    @cooldown(1, 10, BucketType.channel)
    @command(name='help', aliases=['about'])
    @bot_has_permissions(add_reactions=True, embed_links=True,
                         read_message_history=True)  # Message history is for internals of paginate()
    async def base_help(self, ctx, *target):
        """Show this message."""
        if not target:  # No commands - general help
            await self._help_all(ctx)
        elif len(target) == 1:  # Cog or command
            target_name = target[0]
            if target_name in ctx.bot.cogs:
                await self._help_cog(ctx, ctx.bot.cogs[target_name])
            else:
                command = ctx.bot.get_command(target_name)
                if command is None:
                    raise BadArgument('that command/cog does not exist!')
                else:
                    await self._help_command(ctx, command)
        else:  # Command with subcommand
            command = ctx.bot.get_command(' '.join(target))
            if command is None:
                raise BadArgument('that command does not exist!')
            else:
                await self._help_command(ctx, command)

    base_help.example_usage = """
    `{prefix}help` - General help message
    `{prefix}help help` - Help about the help command
    `{prefix}help General` - Help about the General category
    """

    async def _help_all(self, ctx):
        """Gets the help message for all commands."""
        info = discord.Embed(title='Dozer: Info', description='A guild management bot for FIRST Discord servers',
                             color=discord.Color.blue())
        info.set_thumbnail(url=self.bot.user.avatar_url)
        info.add_field(name='About',
                       value="Dozer: A collaborative bot for FIRST Discord servers, developed by the FRC Discord Server Development Team")
        info.add_field(name='About `{}{}`'.format(ctx.prefix, ctx.invoked_with), value=inspect.cleandoc("""
        This command can show info for all commands, a specific command, or a category of commands.
        Use `{0}{1} {1}` for more information.
        """.format(ctx.prefix, ctx.invoked_with)), inline=False)
        info.add_field(name='Support',
                       value="Join our development server at https://discord.gg/bB8tcQ8 for support, to help with development, or if "
                             "you have any questions or comments!")
        info.add_field(name="Open Source",
                       value="Dozer is open source! Feel free to view and contribute to our Python code "
                             "[on Github](https://github.com/FRCDiscord/Dozer)")
        info.set_footer(text='Dozer Help | all commands | Info page')
        await self._show_help(ctx, info, 'Dozer: Commands', '', 'all commands', ctx.bot.commands)

    async def _help_command(self, ctx, command):
        """Gets the help message for one command."""
        info = discord.Embed(title='Command: {}{} {}'.format(ctx.prefix, command.qualified_name, command.signature),
                             description=command.help or (None if command.example_usage else 'No information provided.'),
                             color=discord.Color.blue())
        usage = command.example_usage
        if usage:
            info.add_field(name='Usage', value=usage.format(prefix=ctx.prefix, name=ctx.invoked_with), inline=False)
        info.set_footer(text='Dozer Help | {!r} command | Info'.format(command.qualified_name))
        await self._show_help(ctx, info, 'Subcommands: {prefix}{name} {signature}', '', '{name!r} command',
                              command.commands if isinstance(command, Group) else set(),
                              name=command.qualified_name, signature=command.signature)

    async def _help_cog(self, ctx, cog):
        """Gets the help message for one cog."""
        await self._show_help(ctx, None, 'Category: {cog_name}', inspect.cleandoc(cog.__doc__ or ''),
                              '{cog_name!r} category',
                              (command for command in ctx.bot.commands if command.cog is cog),
                              cog_name=type(cog).__name__)

    async def _show_help(self, ctx, start_page, title, description, footer, commands, **format_args):
        """Creates and sends a template help message, with arguments filled in."""
        format_args['prefix'] = ctx.prefix
        footer = 'Dozer Help | {} | Page {}'.format(footer, '{page_num} of {len_pages}')
        # Page info is inserted as a parameter so page_num and len_pages aren't evaluated now
        if commands:
            command_chunks = list(chunk(sorted(commands, key=lambda cmd: cmd.name), 4))
            format_args['len_pages'] = len(command_chunks)
            pages = []
            for page_num, page_commands in enumerate(command_chunks):
                format_args['page_num'] = page_num + 1
                page = discord.Embed(title=title.format(**format_args), description=description.format(**format_args), color=discord.Color.blue())
                for command in page_commands:
                    if command.short_doc:
                        embed_value = command.short_doc
                    elif command.example_usage:  # Usage provided - show the user the command to see it
                        embed_value = 'Use `{0.prefix}{0.invoked_with} {1.qualified_name}` for more information.'.format(
                            ctx, command)
                    else:
                        embed_value = 'No information provided.'
                    page.add_field(name='{}{} {}'.format(ctx.prefix, command.qualified_name, command.signature), value=embed_value, inline=False)
                page.set_footer(text=footer.format(**format_args))
                pages.append(page)

            if start_page is not None:
                pages.append({'info': start_page})

            if len(pages) == 1:
                await ctx.send(embed=pages[0])
            elif start_page is not None:
                info_emoji = '\N{INFORMATION SOURCE}'
                p = Paginator(ctx, (info_emoji, ...), pages, start='info',
                              auto_remove=ctx.channel.permissions_for(ctx.me))
                async for reaction in p:
                    if reaction == info_emoji:
                        p.go_to_page('info')
            else:
                await paginate(ctx, pages, auto_remove=ctx.channel.permissions_for(ctx.me))
        elif start_page:  # No commands - command without subcommands or empty cog - but a usable info page
            await ctx.send(embed=start_page)
        else:  # No commands, and no info page
            format_args['len_pages'] = 1
            format_args['page_num'] = 1
            embed = discord.Embed(title=title.format(**format_args), description=description.format(**format_args), color=discord.Color.blue())
            embed.set_footer(text=footer.format(**format_args))
            await ctx.send(embed=embed)

    @has_permissions(change_nickname=True)
    @command()
    async def nick(self, ctx, *, nicktochangeto):
        """Allows a member to change their nickname."""
        await discord.Member.edit(ctx.author, nick=nicktochangeto[:32])
        await ctx.send("Nick successfully changed to " + nicktochangeto[:32])
        if len(nicktochangeto) > 32:
            await ctx.send("Warning: truncated nickname to 32 characters")

    @command()
    async def invite(self, ctx):
        """
        Display the bot's invite link.
        The generated link gives all permissions the bot requires. If permissions are removed, some commands will be unusable.
        """
        perms = 0
        for cmd in ctx.bot.walk_commands():
            perms |= cmd.required_permissions.value
        await ctx.send('<{}>'.format(discord.utils.oauth_url(ctx.me.id, discord.Permissions(perms))))

    @has_permissions(create_instant_invite=True)
    @bot_has_permissions(create_instant_invite=True)
    @command()
    async def invites(self, ctx, num, hours=24):
        """
        Generates a set number of single use invites.
        """

        settings = await WelcomeChannel.get_by(guild_id=ctx.guild.id)
        if len(settings) == 0:
            await ctx.send(
                "There is no welcome channel set. Please set one using `{0}welcomeconfig channel` and try again.".format(
                    ctx.prefix))
            return
        else:
            invitechannel = ctx.bot.get_channel(settings[0].channel_id)
            if invitechannel is None:
                await ctx.send(
                    "There was an issue getting your welcome channel. Please set it again using `{0}welcomeconfig channel`.".format(
                        ctx.prefix))
                return
            text = ""
            for i in range(int(num)):
                invite = await invitechannel.create_invite(max_age=hours * 3600, max_uses=1, unique=True,
                                                           reason="Autogenerated by {}".format(ctx.author))
                text += "Invite {0}: <{1}>\n".format(i + 1, invite.url)
            await ctx.send(text)

    invites.example_usage = """
    `{prefix}invtes 5` - Generates 5 single use invites.
    `{prefix}invites 2 12` Generates 2 single use invites that last for 12 hours.
    """

    @command()
    @has_permissions(administrator=True)
    async def welcomeconfig(self, ctx, *, welcome_channel: discord.TextChannel):
        """
        Sets the new member channel for this guild.
        """
        if welcome_channel.guild != ctx.guild:
            await ctx.send("That channel is not in this guild.")
            return
        settings = WelcomeChannel(ctx.guild.id, welcome_channel.id)
        await settings.update_or_add()
        await ctx.send("Welcome channel set to {}".format(welcome_channel.mention))

    welcomeconfig.example_usage = """
    `{prefix}welcomeconfig #new-members` - Sets the invite channel to #new-members.
    """


def setup(bot):
    """Adds the general cog to the bot"""
    bot.remove_command('help')
    bot.add_cog(General(bot))


class WelcomeChannel(db.DatabaseTable):
    """Welcome channel object class"""
    __tablename__ = 'welcome_channel'
    __uniques__ = 'guild_id'

    @classmethod
    async def initial_create(cls):
        """Create the table in the database"""
        async with db.Pool.acquire() as conn:
            await conn.execute(f"""
            CREATE TABLE {cls.__tablename__} (
            guild_id bigint PRIMARY KEY NOT NULL,
            channel_id bigint null
            )""")

    def __init__(self, guild_id, channel_id):
        super().__init__()
        self.guild_id = guild_id
        self.channel_id = channel_id

    @classmethod
    async def get_by(cls, **kwargs):
        results = await super().get_by(**kwargs)
        result_list = []
        for result in results:
            obj = WelcomeChannel(guild_id=result.get("guild_id"), channel_id=result.get("channel_id"))
            result_list.append(obj)
        return result_list