import datetime
import traceback as tb
import discord
import discord.ext.commands as commands
import time
import sys
import six


class DiscordLogs:

    def log(self, prompt: str, **kargs) -> None:
        double_line = kargs["double_line"] if ("double_line" in kargs.keys()) else False
        prompt = f"[{str(datetime.datetime.today())[:-7]}] {prompt}"
        print(("\n" if double_line else "") + prompt)

        if self.logs_file:
            with open(self.logs_file, "a+", encoding="utf-8") as f:
                f.write(("\n\n" if double_line else "\n") + prompt)

    async def send_command_error(self, ctx, err):
        prompt = f"File \'" + tb.extract_tb(err.__traceback__)[1].filename + \
                 f"\', line {tb.extract_tb(err.__traceback__)[1].lineno}, command " \
                 f"\'{tb.extract_tb(err.__traceback__)[1].name}\'\n{err.__class__.__name__} : {err}"

        public_prompt = f"File " + repr(tb.extract_tb(err.__traceback__)[1].filename.split("\\")[
                                            len(tb.extract_tb(err.__traceback__)[1].filename.split("\\")) - 1]) + \
                        f", line {tb.extract_tb(err.__traceback__)[1].lineno}, command " \
                        f"{repr(tb.extract_tb(err.__traceback__)[1].name)}\n{err.__class__.__name__} : {err}"
        self.log("Error raised:\n" + prompt, double_line=True)
        if self.logs_channel:
            embed = discord.Embed(title="Bug raised")
            embed.set_footer(text=f"{self.bot.user.name}" + (f" | ver. {self.version}" if self.version else ""),
                             icon_url=self.bot.user.avatar_url)
            embed.add_field(name="Date", value=str(datetime.datetime.today())[:-7])
            embed.add_field(name="Server", value=f"{ctx.guild.name} ({ctx.guild.id})")
            embed.add_field(name="Command", value=tb.extract_tb(err.__traceback__)[1].name)
            embed.add_field(name="Autor", value=f"{str(ctx.author)} ({ctx.author.id})")
            embed.add_field(name="File", value=tb.extract_tb(err.__traceback__)[1].filename)
            embed.add_field(name="Line", value=tb.extract_tb(err.__traceback__)[1].lineno)
            embed.add_field(name="Error", value=err.__class__.__name__)
            embed.add_field(name="Description", value=err)
            embed.add_field(name="Original message", value=ctx.message.content)
            embed.add_field(name="Link to message", value=ctx.message.jump_url)
            if ctx.guild.me.guild_permissions.manage_guild and await ctx.guild.invites():
                embed.add_field(name="Link to server", value=(await ctx.guild.invites())[0].url)
            elif ctx.channel.permissions_for(ctx.guild.me).create_instant_invite:
                embed.add_field(
                    name="Link to server",
                    value=await ctx.channel.create_invite(reason=self.invite_message_log, max_age=86400, max_uses=1,
                                                          temporary=True, unique=False))
            await self.bot.get_channel(self.logs_channel).send(
                "```" + "".join(tb.format_tb(err.__traceback__)) + "```", embed=embed)

        await ctx.send(self.error_message.format(public_prompt))
        raise err

    def __init__(self, bot: commands.Bot, channel: int, logs: str, version: str):
        self.bot = bot
        self.logs_channel = channel
        self.version = version
        self.logs_file = logs

        self.bad_argument_error = "One or more arguments are incorrect.\nTry ```{}```\n" \
                                  "For more information on usage, send```{}```"
        self.missing_argument_error = "One or more arguments are missing.\nTry ```{}```\n" \
                                      "For more information on usage, send```{}```"
        self.missing_permission_error = "I do not have the necessary permissions to perform this action (role not " \
                                        "high enough or permission not granted)"
        self.user_missing_permission_error = "You do not have the necessary permissions to perform this action (role " \
                                             "not high enough or permission not granted)"
        self.not_found_error = "Sorry, I couldn't find anything that matched what you indicated."
        self.invite_message_log = "A bug has occurred. This invitation will allow, if needed, the developer to " \
                                  "access the server, to understand why the bug occurred. This invitation is limited " \
                                  "to one use, grants only the status of temporary member, and lasts maximum 1 day."
        self.error_message = "An exceptional error has occurred. The bug has been automatically reported, please be " \
                             "patient. Detail of the error :```{}```"

        @self.bot.event
        async def on_command(ctx: commands.Context):
            self.log(f"{repr(ctx.command.name)} command request sent by {repr(str(ctx.author))} "
                     f"({repr(ctx.author.id)})" +
                     (f" with arguments {repr(ctx.message.content[len(self.bot.command_prefix + ctx.invoked_with):])}"
                      if ctx.message.content[len(self.bot.command_prefix + ctx.invoked_with):] else ""))

        @self.bot.event
        async def on_command_completion(ctx: commands.Context):
            rep = None
            async for message in ctx.history(limit=5):
                if message.author == ctx.me:
                    rep = message
                    break
            self.log(f"{repr(ctx.command.name)} command response for {repr(str(ctx.author))} ({repr(ctx.author.id)})" +
                     f" with response {repr(rep.content)}" if rep else "")

        def get_command_signature(prefix: str, command: commands.Command) -> str:
            parent = command.full_parent_name
            if len(command.aliases) > 0:
                aliases = '|'.join(command.aliases)
                fmt = '[%s|%s]' % (command.name, aliases)
                if parent:
                    fmt = parent + ' ' + fmt
                alias = fmt
            else:
                alias = command.name if not parent else parent + ' ' + command.name

            return '%s%s %s' % (prefix, alias, command.signature)

        @self.bot.event
        async def on_command_error(ctx, error):
            if isinstance(error, commands.ConversionError) or isinstance(error, commands.BadArgument):
                await ctx.send(self.bad_argument_error.format(
                    get_command_signature(self.bot.command_prefix, ctx.command),
                    self.bot.command_prefix + "help " + ctx.command.name))
            elif isinstance(error, commands.MissingRequiredArgument):
                await ctx.send(self.missing_argument_error.format(
                    get_command_signature(self.bot.command_prefix, ctx.command),
                    self.bot.command_prefix + "help " + ctx.command.name))
            elif (isinstance(error, commands.CommandInvokeError) and isinstance(error.original, discord.Forbidden)) or \
                    isinstance(error, commands.BotMissingPermissions):
                await ctx.send(self.missing_permission_error)
            elif isinstance(error, commands.CommandInvokeError) and isinstance(error.original, discord.NotFound):
                await ctx.send(self.not_found_error)
            elif isinstance(error, commands.NotOwner) or isinstance(error, commands.NotOwner) or \
                    isinstance(error, commands.MissingPermissions):
                await ctx.send(self.user_missing_permission_error)
            elif not isinstance(error, commands.CommandNotFound):
                if isinstance(error, commands.CommandInvokeError):
                    error = error.original
                self.log(f"{repr(ctx.command.name)} command failed for {repr(str(ctx.author))} ({repr(ctx.author.id)}) "
                         f": {error.__class__.__name__}")
                await self.send_command_error(ctx, error)

        @self.bot.event
        async def on_error(event, *args, **kwargs):
            if event == "on_command_error":
                six.reraise(*sys.exc_info())
            err = sys.exc_info()[2]
            self.log(f"Error raised:\nFile \'" + tb.extract_tb(err)[1].filename +
                     f"\', line {tb.extract_tb(err)[1].lineno}, event {repr(event)} : {str(sys.exc_info()[1])}",
                     double_line=True)
            if self.logs_channel:
                embed = discord.Embed(title="Bug raised")
                embed.set_footer(text=f"{self.bot.user.name}" + (f" | ver. {self.version}" if self.version else ""),
                                 icon_url=self.bot.user.avatar_url)
                embed.add_field(name="Date", value=str(datetime.datetime.today())[:-7])
                embed.add_field(name="Event", value=event)
                embed.add_field(name="File", value=tb.extract_tb(err)[1].filename)
                embed.add_field(name="Line", value=tb.extract_tb(err)[1].lineno)
                embed.add_field(name="Error", value=sys.exc_info()[0].__name__)
                embed.add_field(name="Description", value=str(sys.exc_info()[1]))
                embed.add_field(name="Arguments", value=f"{repr(args)}\n\n{repr(kwargs)}")
                await self.bot.get_channel(self.logs_channel).send(
                    "```" + "".join(tb.format_tb(err)) + "```", embed=embed)
            six.reraise(*sys.exc_info())

        @self.bot.event
        async def on_connect():
            self.log(f"Connected to Discord as \"{bot.user.name}\" (id : {bot.user.id})" +
                     (f" ver. {self.version}" if self.version else ""))

        @self.bot.event
        async def on_ready():
            self.start_time = time.time()
            self.log(f"Bot loaded, ready to use")

        @self.bot.event
        async def on_disconnect():
            self.log(f"Bot disconnected")

        @self.bot.event
        async def on_resumed():
            self.log(f"Bot reconnected")
