'''
MIT License

Copyright (c) 2017-2018 Cree-Py

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''


import discord
import os
import io
import traceback
import textwrap
import inspect
import aiohttp
from motor.motor_asyncio import AsyncIOMotorClient
from contextlib import redirect_stdout
from discord.ext import commands
import json
import subprocess
import asyncio
from ext import utils
from ext.paginator import PaginatorSession


# def load_json(path, key):
#     with open(f'./data/{path}') as f:
#         config = json.load(f)
#     return config.get(key)


async def get_pre(bot, message):
    '''Gets the prefix for the guild'''
    try:
        result = await bot.db.config.find_one({'_id': str(message.guild.id)})
    except AttributeError:
        return '-'
    if not result or not result.get('prefix'):
        return '-'
    return result

bot = commands.Bot(command_prefix="-")
# with open('./data/auths.json') as f:
#     bot.auth = json.load(f)


# dbltoken = load_json('token.json', 'DBLTOKEN')
dbltoken = os.environ.get('dbltoken')
path = 'cogs.'
extensions = [x.replace('.py', '') for x in os.listdir('cogs') if x.endswith('.py')]


def load_extension(cog, path='cogs.'):
    members = inspect.getmembers(cog)
    for name, member in members:
        if name.startswith('on_'):
            bot.add_listener(member, name)
    try:
        bot.load_extension(f'{path}{cog}')
    except Exception as e:
        print(f'LoadError: {cog}\n{type(e).__name__}: {e}')


def load_extensions(cogs, path='cogs.'):
    for cog in cogs:
        members = inspect.getmembers(cog)
        for name, member in members:
            if name.startswith('on_'):
                bot.add_listener(member, name)
        try:
            bot.load_extension(f'{path}{cog}')
        except Exception as e:
            print(f'LoadError: {cog}\n{type(e).__name__}: {e}')


load_extensions(extensions)
bot.remove_command('help')
version = "v2.0.0"


@bot.event
async def on_ready():
    await bot.change_presence(activity=discord.Game(f"with {len(bot.guilds)} servers | -help | {version}"), afk=True)

    url = f"https://discordbots.org/api/bots/{bot.user.id}/stats"
    headers = {
        'Authorization': dbltoken,
        'content-type': 'application/json'
    }
    payload = {
        'server_count': len(bot.guilds)
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=json.dumps(payload), headers=headers) as dblpost:
            print(dblpost.status)

    bot._last_result = None
    bot.session = aiohttp.ClientSession()

    # mongo = AsyncIOMotorClient(bot.auth.get('MONGODB'))
    mongo = AsyncIOMotorClient(os.environ.get('mongodb'))
    bot.db = mongo.RemixBot
    bot.session = aiohttp.ClientSession()

    print('Bot is Online.')


# @bot.event
# async def on_message(message):
#     channel = message.channel
#     if message.author.bot:
#         return

#     if message.content.lower() in ('whatistheprefix', 'what is the prefix'):
#         result = await bot.db.config.find_one({'_id': str(message.guild.id)})
#         if not result or not result.get('prefix'):
#             prefix = '-'
#         else:
#             prefix = result.get('prefix')
#         await channel.send(f'The guild prefix is `{prefix}`')

#     await bot.process_commands(message)


@bot.event
async def on_guild_join(g):
    success = False
    i = 0
    while not success:
        try:
            await g.channels[i].send(f"Hello! Thanks for inviting me to your server. To set a custom prefix, use `-prefix <prefix>`. For more help, use `-help`.")
        except (discord.Forbidden, AttributeError):
            i += 1
        except IndexError:
            # if the server has no channels, doesn't let the bot talk, or all vc/categories
            pass
        else:
            success = True

    url = f"https://discordbots.org/api/bots/{bot.user.id}/stats"
    headers = {
        'Authorization': dbltoken,
        'content-type': 'application/json'
    }
    payload = {
        'server_count': len(bot.guilds)
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=json.dumps(payload), headers=headers) as dblpost:
            print(dblpost.status)

    await bot.change_presence(activity=discord.Game(f"with {len(bot.guilds)} servers | -help | {version}"), afk=True)


@bot.event
async def on_guild_remove(g):
    url = f"https://discordbots.org/api/bots/{bot.user.id}/stats"
    headers = {
        'Authorization': dbltoken,
        'content-type': 'application/json'
    }
    payload = {
        'server_count': len(bot.guilds)
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=json.dumps(payload), headers=headers) as dblpost:
            print(dblpost.status)

    await bot.change_presence(activity=discord.Game(f"with {len(bot.guilds)} servers | -help | {version}"), afk=True)


async def send_cmd_help(ctx):
    cmd = ctx.command
    em = discord.Embed(title=f'Usage: {ctx.prefix + cmd.signature}')
    em.color = discord.Color.green()
    em.description = cmd.help
    return em


@bot.event
async def on_command_error(ctx, error):

    send_help = (commands.MissingRequiredArgument, commands.BadArgument, commands.TooManyArguments, commands.UserInputError)

    if isinstance(error, commands.CommandNotFound):  # fails silently
        pass

    elif isinstance(error, send_help):
        _help = await send_cmd_help(ctx)
        await ctx.send(embed=_help)

    elif isinstance(error, commands.CommandOnCooldown):
        await ctx.send(f'This command is on cooldown. Please wait {error.retry_after:.2f}s')

    elif isinstance(error, commands.MissingPermissions):
        await ctx.send('You do not have the permissions to use this command.')
    # If any other error occurs, prints to console.
    else:
        print(''.join(traceback.format_exception(type(error), error, error.__traceback__)))


def format_command_help(ctx, cmd):
    '''Format help for a command'''
    color = discord.Color.green()
    em = discord.Embed(color=color, description=cmd.help)

    if hasattr(cmd, 'invoke_without_command') and cmd.invoke_without_command:
        em.title = f'`Usage: {ctx.prefix}{cmd.signature}`'
    else:
        em.title = f'`{ctx.prefix}{cmd.signature}`'

    return em


def format_cog_help(ctx, cog):
    '''Format help for a cog'''
    signatures = []
    color = discord.Color.green()
    em = discord.Embed(color=color, description=f'*{inspect.getdoc(cog)}*')
    em.title = type(cog).__name__.replace('_', ' ')
    cc = []
    for cmd in bot.commands:
        if not cmd.hidden:
            if cmd.instance is cog:
                cc.append(cmd)
                signatures.append(len(cmd.name) + len(ctx.prefix))
    max_length = max(signatures)
    abc = sorted(cc, key=lambda x: x.name)
    cmds = ''
    for c in abc:
        cmds += f'`{ctx.prefix + c.name:<{max_length}} '
        cmds += f'{c.short_doc:<{max_length}}`\n'
    em.add_field(name='Commands', value=cmds)

    return em


def format_bot_help(ctx):
    signatures = []
    fmt = ''
    commands = []
    for cmd in bot.commands:
        if not cmd.hidden:
            if type(cmd.instance).__name__ == 'NoneType':
                commands.append(cmd)
                signatures.append(len(cmd.name) + len(ctx.prefix))
    max_length = max(signatures)
    abc = sorted(commands, key=lambda x: x.name)
    for c in abc:
        fmt += f'`{ctx.prefix + c.name:<{max_length}} '
        fmt += f'{c.short_doc:<{max_length}}`\n'
    em = discord.Embed(title='Bot', color=discord.Color.green())
    em.description = '*Commands for the main bot.*'
    em.add_field(name='Commands', value=fmt)

    return em


@bot.command()
async def help(ctx, *, command: str=None):
    '''Shows this message'''

    if command is not None:
        aliases = {
            'clash of clans': 'Clash_of_Clans',
            'coc': 'Clash_of_Clans',
            'cr': 'Clash_Royale',
            'Clash Of Clans': 'Clash_of_Clans',
            'utils': 'Utility'
        }

        if command.lower() in aliases.keys():
            command = aliases[command]

        cog = bot.get_cog(command.replace(' ', '_').title())
        cmd = bot.get_command(command)
        if cog is not None:
            em = format_cog_help(ctx, cog)
        elif cmd is not None:
            em = format_command_help(ctx, cmd)
        else:
            await ctx.send('No commands found.')
        return await ctx.send(embed=em)

    pages = []
    for cog in bot.cogs.values():
        em = format_cog_help(ctx, cog)
        pages.append(em)
    em = format_bot_help(ctx)
    pages.append(em)

    p_session = PaginatorSession(ctx, footer=f'Type {ctx.prefix}help command for more info on a command.', pages=pages)
    await p_session.run()


@bot.command()
async def ping(ctx):
    '''Pong! Get the bot's response time'''
    em = discord.Embed(color=discord.Color.green())
    em.title = "Pong!"
    em.description = f'{bot.latency * 1000} ms'
    await ctx.send(embed=em)


@bot.command(name='bot')
async def _bot(ctx):
    '''Shows info about bot'''
    em = discord.Embed(color=discord.Color.green())
    em.title = 'Bot Info'
    em.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
    try:
        em.description = bot.psa + '\n[Support Server](https://discord.gg/RzsYQ9f)'
    except AttributeError:
        em.description = 'A multipurpose bot made by SharpBit, Victini, AntonyJoseph03, Free TNT and Sleedyak.\n[Support Server](https://discord.gg/RzsYQ9f)'
    em.add_field(name="Servers", value=len(bot.guilds))
    em.add_field(name="Online Users", value=str(len({m.id for m in bot.get_all_members() if m.status is not discord.Status.offline})))
    em.add_field(name='Total Users', value=len(bot.users))
    em.add_field(name='Channels', value=f"{sum(1 for g in bot.guilds for _ in g.channels)}")
    em.add_field(name="Library", value=f"discord.py")
    em.add_field(name="Bot Latency", value=f"{bot.ws.latency * 1000:.0f} ms")
    em.add_field(name="Invite", value=f"[Click Here](https://discordapp.com/oauth2/authorize?client_id={bot.user.id}&scope=bot&permissions=268905542)")
    em.add_field(name='GitHub', value='[Click here](https://github.com/cree-py/RemixBot)')
    em.add_field(name="Upvote this bot!", value=f"[Click here](https://discordbots.org/bot/{bot.user.id}) :reminder_ribbon:")

    em.set_footer(text="RemixBot | Powered by discord.py")
    await ctx.send(embed=em)


@bot.command(hidden=True)
@utils.developer()
async def psa(ctx, *, message):
    '''Tells everyone an announcement in the bot info command.'''
    bot.psa = None if message == 'reset' else message
    await ctx.send('PSA successfully set.')


@bot.command(name='eval', hidden=True)
@utils.developer()
async def _eval(ctx, *, body):
    """Evaluates python code"""
    env = {
        'ctx': ctx,
        'channel': ctx.channel,
        'author': ctx.author,
        'guild': ctx.guild,
        'message': ctx.message,
        '_': bot._last_result,
        'source': inspect.getsource,
        'session': bot.session
    }

    env.update(globals())
    body = utils.cleanup_code(body)
    stdout = io.StringIO()
    err = out = None
    to_compile = f'async def func(): \n{textwrap.indent(body, "  ")}'
    try:
        exec(to_compile, env)
    except Exception as e:
        err = await ctx.send(f'```py\n{e.__class__.__name__}: {e}\n```')
        return await ctx.message.add_reaction('\u2049')
    func = env['func']
    try:
        with redirect_stdout(stdout):
            ret = await func()
    except Exception as e:
        value = stdout.getvalue()
        err = await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
    else:
        value = stdout.getvalue()
        if ret is None:
            if value:
                try:
                    out = await ctx.send(f'```py\n{value}\n```')
                except:
                    paginated_text = utils.paginate(value)
                    for page in paginated_text:
                        if page == paginated_text[-1]:
                            out = await ctx.send(f'```py\n{page}\n```')
                            break
                        await ctx.send(f'```py\n{page}\n```')
        else:
            bot._last_result = ret
            try:
                out = await ctx.send(f'```py\n{value}{ret}\n```')
            except:
                paginated_text = utils.paginate(f"{value}{ret}")
                for page in paginated_text:
                    if page == paginated_text[-1]:
                        out = await ctx.send(f'```py\n{page}\n```')
                        break
                    await ctx.send(f'```py\n{page}\n```')
    if out:
        await ctx.message.add_reaction('\u2705')  # tick
    elif err:
        await ctx.message.add_reaction('\u2049')  # x
    else:
        await ctx.message.add_reaction('\u2705')


@bot.command(hidden=True)
@utils.developer()
async def reload(ctx, cog):
    """Reloads a cog"""
    if cog.lower() == 'all':
        for cog in extensions:
            try:
                bot.unload_extension(f"cogs.{cog}")
            except Exception as e:
                await ctx.send(f"An error occured while reloading {cog}, error details: \n ```{e}```")
        load_extensions(extensions)
        return await ctx.send('All cogs updated successfully :white_check_mark:')
    if cog not in extensions:
        return await ctx.send(f'Cog {cog} does not exist.')
    try:
        bot.unload_extension(f"cogs.{cog}")
        await asyncio.sleep(1)
        load_extension(cog)
    except Exception as e:
        await ctx.send(f"An error occured while reloading {cog}, error details: \n ```{e}```")
    else:
        await ctx.send(f"Reloaded the {cog} cog successfully :white_check_mark:")


@bot.command(hidden=True)
@utils.developer()
async def update(ctx):
    """Pulls from github and updates bot"""
    await ctx.trigger_typing()
    await ctx.send(f"```{subprocess.run('git pull', stdout=subprocess.PIPE).stdout.decode('utf-8')}```")
    for cog in extensions:
        bot.unload_extension(f'{path}{cog}')
    for cog in extensions:
        members = inspect.getmembers(cog)
        for name, member in members:
            if name.startswith('on_'):
                bot.add_listener(member, name)
        try:
            bot.load_extension(f'{path}{cog}')
        except Exception as e:
            await ctx.send(f'LoadError: {cog}\n{type(e).__name__}: {e}')
    await ctx.send('All cogs reloaded :white_check_mark:')


@bot.command()
async def invite(ctx):
    '''Invite the bot to your server'''
    await ctx.send(
        f"Invite me to your server: https://discordapp.com/oauth2/authorize?client_id=384044025298026496&scope=bot&permissions=268905542"
    )


@bot.command()
async def exec(ctx, *, command):
    '''Use shell commands.'''
    ret = subprocess.run(command, stdout=subprocess.PIPE).stdout.decode('utf-8')
    await ctx.send(f'```\n{ret}\n```')


@bot.command(hidden=True)
@utils.developer()
async def shutdown(ctx):
    '''Shut down the bot'''
    await ctx.send("Shutting down....")
    await bot.logout()

# if __name__ == "main":
#     print('Online.')
# else:
#     print('GET THE FUCK OUT CODING COPIER AND NOOB XDDDDDD')

if __name__ == '__main__':
    # bot.run(load_json('token.json', 'TOKEN'))
    # print('Bot is online.')
    bot.run(os.environ.get('token'))