import rethinkdb as r
from discord.ext import commands
from discord.ext.commands.cooldowns import BucketType

from . import utils

import discord
import random
import functools
import asyncio

battle_outcomes = \
    ["A meteor fell on {loser}, {winner} is left standing and has been declared the victor!",
     "{loser} was shot through the heart, and {winner} is to blame",
     "{winner} has bucked {loser} into a tree, even Big Mac would be impressed at that kick!",
     "As they were battling, {loser} was struck by lightning! {winner} you lucked out this time!",
     "{loser} tried to dive at {winner} while fighting, somehow they missed and landed in quicksand."
     "Try paying more attention next time {loser}",
     "{loser} got a little...heated during the battle and ended up getting set on fire. {winner} wins by remaining cool",
     "Princess Celestia came in and banished {loser} to the moon. Good luck getting into any battles up there",
     "{loser} took an arrow to the knee, they are no longer an adventurer. Keep on adventuring {winner}",
     "Common sense should make it obvious not to get into battle with {winner}. Apparently {loser} didn't get the memo",
     "{winner} had a nice cup of tea with {loser} over their conflict, and mutually agreed that {winner} was Best Pony",
     "{winner} and {loser} had an intense staring contest. "
     "Sadly, {loser} forgot to breathe and lost much morethan the staring contest",
     "It appears {loser} is actually a pacifist, they ran away screaming and crying. "
     "Maybe you should have thought of that before getting in a fight?",
     "A bunch of parasprites came in and ate up the jetpack while {loser} was flying with it. Those pesky critters...",
     "{winner} used their charm to seduce {loser} to surrender.",
     "{loser} slipped on a banana peel and fell into a pit of spikes. That's actually impressive.",
     "{winner} realized it was high noon, {loser} never even saw it coming.",
     "{loser} spontaneously combusted...lol rip",
     "after many turns {winner} summons exodia and {loser} is sent to the shadow realm",
     "{winner} and {loser} sit down for an intense game of chess, in the heat of the moment {winner} forgot they were playing a "
     "game and summoned a real knight",
     "{winner} challenges {loser} to rock paper scissors, unfortunately for {loser}, {winner} chose scissors and stabbed them",
     "{winner} goes back in time and becomes {loser}'s best friend, winning without ever throwing a punch",
     "{loser} trips down some stairs on their way to the battle with {winner}",
     "{winner} books {loser} a one way ticket to Flugendorf prison",
     "{loser} was already dead",
     "{loser} was crushed under the weight of expectations",
     "{loser} was wearing a redshirt and it was their first day",
     "{winner} and {loser} were walking along when suddenly {loser} got kidnapped by a flying monkey; hope they had water with them",
     "{winner} brought an army to a fist fight, {loser} never saw their opponent once",
     "{winner} used multiple simultaneous devestating defensive deep strikes to overwhelm {loser}",
     "{winner} and {loser} engage in a dance off; {winner} wiped the floor with {loser}",
     "{loser} tried to hide in the sand to catch {winner} off guard, unfortunately looks like a Giant Antlion had the same "
     "idea for him",
     "{loser} was busy playing trash videogames the night before the fight and collapsed before {winner}",
     "{winner} threw a sick meme and {loser} totally got PRANK'D",
     "{winner} and {loser} go on a skiing trip together, turns out {loser} forgot how to pizza french-fry",
     "{winner} is the cure and {loser} is the disease....well {loser} was the disease",
     "{loser} talked their mouth off at {winner}...literally...",
     "Looks like {loser} didn't put enough points into kazoo playing, who knew they would have needed it",
     "{loser} was too scared by the illuminati and extra-dimensional talking horses to show up",
     "{loser} didn't press x enough to not die",
     "{winner} and {loser} go fishing to settle their debate, {winner} caught a sizeable fish and {loser} caught a boot older than time",
     "{winner} did a hero landing and {loser} was so surprised they gave up immediately"]

hugs = \
    ["*hugs {user}.*",
     "*tackles {user} for a hug.*",
     "*drags {user} into her dungeon where hugs ensue*",
     "*pulls {user} to the side for a warm hug*",
     "*goes out to buy a big enough blanket to embrace {user}*",
     "*hard codes an electric hug to {user}*",
     "*hires mercenaries to take {user} out....to a nice dinner*",
     "*pays $10 to not touch {user}*",
     "*clones herself to create a hug pile with {user}*",
     "*orders an airstrike of hugs {user}*",
     "*glomps {user}*",
     "*hears a knock at her door, opens it, finds {user} and hugs them excitedly*",
     "*goes in for a punch but misses and ends up hugging {user}*",
     "*hugs {user} from behind*",
     "*denies a hug from {user}*",
     "*does a hug to {user}*",
     "*lets {user} cuddle nonchalantly*",
     "*cuddles {user}*",
     "*burrows underground and pops up underneath {user} she hugs their legs.*",
     "*approaches {user} after having gone to the gym for several months and almost crushes them.*"]


class Interaction:
    """Commands that interact with another user"""

    def __init__(self, bot):
        self.bot = bot
        self.battles = {}
        self.bot.br = BattleRankings(self.bot)
        self.bot.br.update_start()

    def get_battle(self, player):
        battles = self.battles.get(player.guild.id)

        if battles is None:
            return None

        for battle in battles:
            if battle['p2'] == player.id:
                return battle

    def can_battle(self, player):
        battles = self.battles.get(player.guild.id)

        if battles is None:
            return True

        for x in battles:
            if x['p1'] == player.id:
                return False
        return True

    def can_be_battled(self, player):
        battles = self.battles.get(player.guild.id)

        if battles is None:
            return True

        for x in battles:
            if x['p2'] == player.id:
                return False
        return True

    def start_battle(self, player1, player2):
        battles = self.battles.get(player1.guild.id, [])
        entry = {
            'p1': player1.id,
            'p2': player2.id
        }
        battles.append(entry)
        self.battles[player1.guild.id] = battles

    # Handles removing the author from the dictionary of battles
    def battling_off(self, player1=None, player2=None):
        if player1:
            guild = player1.guild.id
        else:
            guild = player2.guild.id
        battles = self.battles.get(guild, [])
        # Create a new list, exactly the way the last one was setup
        # But don't include the one start with player's ID
        new_battles = []
        for b in battles:
            if player1 and b['p1'] == player1.id:
                continue
            if player2 and b['p2'] == player2.id:
                continue
            new_battles.append(b)
        self.battles[guild] = new_battles

    @commands.command()
    @commands.guild_only()
    @utils.custom_perms(send_messages=True)
    @utils.check_restricted()
    async def hug(self, ctx, user = None):
        """Makes me hug a person!

        EXAMPLE: !hug @Someone
        RESULT: I hug the shit out of that person"""
        if ctx.message.mention_everyone:
            await ctx.send("Your arms aren't big enough")
            return
        if user is None:
            user = ctx.message.author
        else:
            converter = commands.converter.MemberConverter()
            try:
                user = await converter.convert(ctx, user)
            except commands.converter.BadArgument:
                await ctx.send("Error: Could not find user: {}".format(user))
                return


        # Lets get the settings
        settings = self.bot.db.load('server_settings', key=ctx.message.guild.id) or {}
        # Get the custom messages we can use
        custom_msgs = settings.get('hugs')
        default_on = settings.get('default_hugs')
        # if they exist, then we want to see if we want to use default as well
        if custom_msgs:
            if default_on or default_on is None:
                msgs = hugs + custom_msgs
            else:
                msgs = custom_msgs
        # Otherwise we simply just want to use the default, no matter what the default setting is
        else:
            msgs = hugs

        fmt = random.SystemRandom().choice(msgs)
        await ctx.send(fmt.format(user=user.display_name))

    @commands.command(aliases=['1v1'])
    @commands.guild_only()
    @commands.cooldown(1, 20, BucketType.user)
    @utils.custom_perms(send_messages=True)
    @utils.check_restricted()
    async def battle(self, ctx, player2 = None):
        """Challenges the mentioned user to a battle

        EXAMPLE: !battle @player2
        RESULT: A battle to the death"""
        # First check if everyone was mentioned
        if ctx.message.mention_everyone:
            await ctx.send("You want to battle {} people? Good luck with that...".format(len(ctx.message.channel.members) - 1))
            return
        # Then check if nothing was provided
        if player2 is None:
            await ctx.send("Who are you trying to battle...?")
            return
        else:
            # Otherwise, try to convert to an actual member
            converter = commands.converter.MemberConverter()
            try:
                player2 = await converter.convert(ctx, player2)
            except commands.converter.BadArgument:
                await ctx.send("Error: Could not find user: {}".format(player2))
                return
        # Then check if the person used is the author
        if ctx.message.author.id == player2.id:
            ctx.command.reset_cooldown(ctx)
            await ctx.send("Why would you want to battle yourself? Suicide is not the answer")
            return
        # Check if the person battled is me
        if self.bot.user.id == player2.id:
            ctx.command.reset_cooldown(ctx)
            await ctx.send("I always win, don't even try it.")
            return
        # Next two checks are to see if the author or person battled can be battled
        if not self.can_battle(ctx.message.author):
            ctx.command.reset_cooldown(ctx)
            await ctx.send("You are already battling someone!")
            return
        if not self.can_be_battled(player2):
            ctx.command.reset_cooldown(ctx)
            await ctx.send("{} is already being challenged to a battle!".format(player2))
            return

        # Add the author and player provided in a new battle
        self.start_battle(ctx.message.author, player2)

        fmt = "{0.message.author.mention} has challenged you to a battle {1.mention}\n" \
              "{0.prefix}accept or {0.prefix}decline"
        # Add a call to turn off battling, if the battle is not accepted/declined in 3 minutes
        part = functools.partial(self.battling_off, player1=ctx.message.author)
        self.bot.loop.call_later(180, part)
        await ctx.send(fmt.format(ctx, player2))

    @commands.command()
    @commands.guild_only()
    @utils.custom_perms(send_messages=True)
    @utils.check_restricted()
    async def accept(self, ctx):
        """Accepts the battle challenge

        EXAMPLE: !accept
        RESULT: Hopefully the other person's death"""
        # This is a check to make sure that the author is the one being BATTLED
        # And not the one that started the battle
        battle = self.get_battle(ctx.message.author)
        if battle is None:
            await ctx.send("You are not currently being challenged to a battle!")
            return

        battleP1 = discord.utils.find(lambda m: m.id == battle['p1'], ctx.message.guild.members)
        if battleP1 is None:
            await ctx.send("The person who challenged you to a battle has apparently left the server....why?")
            return

        battleP2 = ctx.message.author

        # Lets get the settings
        settings = self.bot.db.load('server_settings', key=ctx.message.guild.id) or {}
        # Get the custom messages we can use
        custom_msgs = settings.get('battles')
        default_on = settings.get('default_battles')
        # if they exist, then we want to see if we want to use default as well
        if custom_msgs:
            if default_on or default_on is None:
                msgs = battle_outcomes + custom_msgs
            else:
                msgs = custom_msgs
        # Otherwise we simply just want to use the default, no matter what the default setting is
        else:
            msgs = battle_outcomes

        fmt = random.SystemRandom().choice(msgs)
        # Due to our previous checks, the ID should only be in the dictionary once, in the current battle we're checking
        self.battling_off(player2=ctx.message.author)
        await self.bot.br.update()

        # Randomize the order of who is printed/sent to the update system
        if random.SystemRandom().randint(0, 1):
            winner = battleP1
            loser = battleP2
        else:
            winner = battleP2
            loser = battleP1

        msg = await ctx.send(fmt.format(winner=winner.display_name, loser=loser.display_name))
        old_winner_rank, _ = self.bot.br.get_server_rank(winner)
        old_loser_rank, _ = self.bot.br.get_server_rank(loser)

        # Update our records; this will update our cache
        await utils.update_records('battle_records', self.bot.db, winner, loser)
        # Now wait a couple seconds to ensure cache is updated
        await asyncio.sleep(2)
        await self.bot.br.update()

        # Now get the new ranks after this stuff has been updated
        new_winner_rank, _ = self.bot.br.get_server_rank(winner)
        new_loser_rank, _ = self.bot.br.get_server_rank(loser)
        fmt = msg.content
        if old_winner_rank:
            fmt += "\n{} - Rank: {} ( +{} )".format(winner.display_name, new_winner_rank, old_winner_rank - new_winner_rank)
        else:
            fmt += "\n{} - Rank: {}".format(winner.display_name, new_winner_rank)
        if old_loser_rank:
            fmt += "\n{} - Rank: {} ( -{} )".format(loser.display_name, new_loser_rank, new_loser_rank - old_loser_rank)
        else:
            fmt += "\n{} - Rank: {}".format(loser.display_name, new_loser_rank)

        try:
            await msg.edit(content=fmt)
        except:
            pass

    @commands.command()
    @commands.guild_only()
    @utils.custom_perms(send_messages=True)
    @utils.check_restricted()
    async def decline(self, ctx):
        """Declines the battle challenge

        EXAMPLE: !decline
        RESULT: You chicken out"""
        # This is a check to make sure that the author is the one being BATTLED
        # And not the one that started the battle
        battle = self.get_battle(ctx.message.author)
        if battle is None:
            await ctx.send("You are not currently being challenged to a battle!")
            return

        battleP1 = discord.utils.find(lambda m: m.id == battle['p1'], ctx.message.guild.members)
        if battleP1 is None:
            await ctx.send("The person who challenged you to a battle has apparently left the server....why?")
            return

        battleP2 = ctx.message.author

        # There's no need to update the stats for the members if they declined the battle
        self.battling_off(player2=battleP2)
        await ctx.send("{} has chickened out! What a loser~".format(battleP2.mention))

    @commands.command()
    @commands.guild_only()
    @commands.cooldown(1, 10, BucketType.user)
    @utils.custom_perms(send_messages=True)
    @utils.check_restricted()
    async def boop(self, ctx, boopee: discord.Member = None, *, message=""):
        """Boops the mentioned person

        EXAMPLE: !boop @OtherPerson
        RESULT: You do a boop o3o"""
        booper = ctx.message.author
        if boopee is None:
            ctx.command.reset_cooldown(ctx)
            await ctx.send("You try to boop the air, the air boops back. Be afraid....")
            return
        # To keep formatting easier, keep it either "" or the message with a space in front
        if message is not None:
            message = " " + message
        if boopee.id == booper.id:
            ctx.command.reset_cooldown(ctx)
            await ctx.send("You can't boop yourself! Silly...")
            return
        if boopee.id == self.bot.user.id:
            ctx.command.reset_cooldown(ctx)
            await ctx.send("Why the heck are you booping me? Get away from me >:c")
            return

        key = str(booper.id)
        boops = self.bot.db.load('boops', key=key, pluck='boops') or {}
        amount = boops.get(str(boopee.id), 0) + 1
        entry = {
            'member_id': str(booper.id),
            'boops': {
                str(boopee.id): amount
            }
        }
        self.bot.db.save('boops', entry)

        fmt = "{0.mention} has just booped {1.mention}{3}! That's {2} times now!"
        await ctx.send(fmt.format(booper, boopee, amount, message))


# noinspection PyMethodMayBeStatic
class BattleRankings:
    def __init__(self, bot):
        self.db = bot.db
        self.loop = bot.loop
        self.ratings = None

    def build_dict(self, seq, key):
        return dict((d[key], dict(d, rank=index + 1)) for (index, d) in enumerate(seq[::-1]))

    def update_start(self):
        self.loop.create_task(self.update())

    async def update(self):
        ratings = await self.db.query(r.table('battle_records').order_by('rating'))

        # Create a dictionary so that we have something to "get" from easily
        self.ratings = self.build_dict(ratings, 'member_id')

    def get_record(self, member):
        data = self.ratings.get(str(member.id), {})
        fmt = "{} - {}".format(data.get('wins'), data.get('losses'))
        return fmt

    def get_rating(self, member):
        data = self.ratings.get(str(member.id), {})
        return data.get('rating')

    def get_rank(self, member):
        data = self.ratings.get(str(member.id), {})
        return data.get('rank'), len(self.ratings)

    def get_server_rank(self, member):
        # Get the id's of all the members to compare to
        server_ids = [str(m.id) for m in member.guild.members]
        # Get all the ratings for members in this server
        ratings = [x for x in self.ratings.values() if x['member_id'] in server_ids]
        # Since we went from a dictionary to a list, we're no longer sorted, sort this
        ratings = sorted(ratings, key=lambda x: x['rating'])
        # Build our dictionary to get correct rankings
        server_ratings = self.build_dict(ratings, 'member_id')
        # Return the rank
        return server_ratings.get(str(member.id), {}).get('rank'), len(server_ratings)


def setup(bot):
    bot.add_cog(Interaction(bot))
