"""
Classes used to handle bot events and errors.

*Classes*
---------
    EventHandler
    ErrorHandler
"""
import traceback  # Used to get the traceback of errors
import sys  # Used to get traceback when the specific error is not
            # available

import discord  # Used to init discord.Game and discord.Status, as well
                # as compare errors to discord errors and as typehints
from discord.ext import commands  # Used to compare errors with command
                                  # errors

from discord_slash.context import SlashContext
from gwendolyn.utils.util_functions import emoji_to_command


class EventHandler():
    """
    Handles bot events.

    *Methods*
    ---------
        on_ready()
        on_slash_command(ctx: discord_slash.context.SlashContext)
        on_reaction_add(ctx: discord_slash.context.SlashContext)
    """

    def __init__(self, bot):
        """Initialize the handler."""
        self.bot = bot

    async def on_ready(self):
        """Log and sets status when it logs in."""
        await self.bot.database_funcs.imdb_commands()
        name = self.bot.user.name
        userid = str(self.bot.user.id)
        logged_in_message = f"Logged in as {name}, {userid}"
        self.bot.log(logged_in_message, level=25)
        game = discord.Game("Use /help for commands")

        online_status = discord.Status.online
        await self.bot.change_presence(activity=game, status=online_status)

    async def on_slash_command(self, ctx: SlashContext):
        """Log when a slash command is given."""
        if ctx.subcommand_name is not None:
            subcommand = f" {ctx.subcommand_name} "
        else:
            subcommand = " "

        if ctx.subcommand_group is not None:
            sub_command_group = f"{ctx.subcommand_group} "
        else:
            sub_command_group = ""

        args = " ".join([str(i) for i in ctx.args])
        full_command = f"/{ctx.command}{subcommand}{sub_command_group}{args}"
        log_message = f"{ctx.author.display_name} ran {full_command}"
        self.bot.log(log_message, str(ctx.channel_id), level=25)

    async def on_reaction_add(self, reaction: discord.Reaction,
                              user: discord.User):
        """Take action if the reaction is on a command message."""
        if not user.bot:
            tests = self.bot.database_funcs
            message = reaction.message
            channel = message.channel
            reacted_message = f"{user.display_name} reacted to a message"
            self.bot.log(reacted_message, str(channel.id))
            plex_data = tests.bedre_netflix_reaction_test(message)
            # plex_data is a list containing 3 elements: whether it was
            # the add_show/add_movie command message the reaction was to
            # (bool), whether it's a movie (bool) (if false, it's a
            # show), and the imdb ids/names for the for the movies or
            # shows listed in the message (list).

            reaction_test_parameters = [message, f"#{str(user.id)}"]

            if tests.connect_four_reaction_test(*reaction_test_parameters):
                column = emoji_to_command(reaction.emoji)
                params = [message, f"#{user.id}", column-1]
                await self.bot.games.connect_four.placePiece(*params)

            if plex_data[0]:
                plex_functions = self.bot.other.bedre_netflix
                if plex_data[1]:
                    movie_pick = emoji_to_command(reaction.emoji)
                    if movie_pick == "none":
                        imdb_id = None
                    else:
                        imdb_id = plex_data[2][movie_pick-1]

                    if isinstance(channel, discord.DMChannel):
                        await message.delete()
                        await plex_functions.add_movie(message, imdb_id, False)
                    else:
                        await message.clear_reactions()
                        await plex_functions.add_movie(message, imdb_id)
                else:
                    show_pick = emoji_to_command(reaction.emoji)
                    if show_pick == "none":
                        imdb_name = None
                    else:
                        imdb_name = plex_data[2][show_pick-1]

                    if isinstance(channel, discord.DMChannel):
                        await message.delete()
                        await plex_functions.add_show(message, imdb_name, False)
                    else:
                        await message.clear_reactions()
                        await plex_functions.add_show(message, imdb_name)

            elif tests.hangman_reaction_test(*reaction_test_parameters):
                self.bot.log("They reacted to the hangman message")
                if ord(reaction.emoji) in range(127462, 127488):
                    # The range is letter-emojis
                    guess = chr(ord(reaction.emoji)-127397)
                    # Converts emoji to letter
                    params = [message, f"#{user.id}", guess]
                    await self.bot.games.hangman.guess(*params)
                else:
                    self.bot.log("Bot they didn't react with a valid guess")


class ErrorHandler():
    """
    Handles errors.

    *Methods*
    ---------
        on_slash_command_error(ctx: discord_slash.context.SlashContext,
                               error: Exception)
        on_error(method: str)
    """

    def __init__(self, bot):
        """Initialize the handler."""
        self.bot = bot

    async def on_slash_command_error(self, ctx: SlashContext,
                                     error: Exception):
        """Log when there's a slash command."""
        if isinstance(error, commands.CommandNotFound):
            await ctx.send("That's not a command")
        elif isinstance(error, discord.errors.NotFound):
            self.bot.log("Deleted message before I could add all reactions")
        elif isinstance(error, commands.errors.MissingRequiredArgument):
            self.bot.log(f"{error}", str(ctx.channel_id))
            await ctx.send(self.bot.long_strings["missing parameters"])
        else:
            params = [type(error), error, error.__traceback__]
            exception = traceback.format_exception(*params)

            exception_string = "".join(exception)
            log_messages = [f"exception in /{ctx.name}", f"{exception_string}"]
            self.bot.log(log_messages, str(ctx.channel_id), 40)
            if isinstance(error, discord.errors.NotFound):
                self.bot.log("Context is non-existant", level=40)
            else:
                await ctx.send("Something went wrong (error code 000)")

    async def on_error(self, method: str):
        """Log when there's an error."""
        error_type = sys.exc_info()[0]
        if error_type == discord.errors.NotFound:
            self.bot.log("Deleted message before I could add all reactions")
        else:
            exception = traceback.format_exc()

            exception_string = "".join(exception)
            log_messages = [f"exception in {method}", f"{exception_string}"]
            self.bot.log(log_messages, level=40)
