import asyncio
import logging
import traceback
import threading
from enum import Enum

import discord
from discord.ext import commands
from django.conf import settings
from django.shortcuts import reverse
from minecraft_manager.models import Application, Ticket
from minecraft_manager.utils import url_path

logger = logging.getLogger(__name__)

discord_loop = None
discord_bot = None
description = '''
A Discord bot connected to an MCM instance.
'''


class DiscordStatus(Enum):
    STOPPED = 0
    STARTING = 1
    STARTED = 2
    STOPPING = 3

    def __str__(self):
        return self.name.title()

    def is_running(self):
        return self != DiscordStatus.STOPPED


discord_status = DiscordStatus.STOPPED


class Discord(commands.Bot):
    discord_game = 'MCM'
    prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!')
    auth_roles = getattr(settings, 'DISCORD_BOT_ROLES', [])
    superuser_roles = getattr(settings, 'DISCORD_SUPERUSER_ROLES', [])
    error_users = getattr(settings, 'DISCORD_ERROR_USERS', [])

    def __init__(self, token, loop):
        super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None, activity=discord.Game(name=self.discord_game), loop=loop)
        self.token = token
        self.load_extension("minecraft_manager.bot.commands")

    async def on_ready(self):
        global discord_status
        discord_status = DiscordStatus.STARTED
        print('Logged in as')
        print(self.user.name)
        print(self.user.id)
        print(discord.__version__)
        print('Voice Loaded: {0}'.format(discord.opus.is_loaded()))
        print('OAuth URL: https://discordapp.com/api/oauth2/authorize?client_id={0}&permissions=0&scope=bot'.format(self.user.id))
        print('------')
        print('Logged in as {0} ({1}) with discord.py v{2}'.format(self.user.name, self.user.id, discord.__version__))

        channel_id = getattr(settings, 'DISCORD_MCM_CHANNEL', None)
        if channel_id:
            channel = self.get_channel(channel_id)
            embed = discord.Embed(color=8311585)
            content = ""
            unanswered_applications = Application.objects.filter(accepted=None)
            if len(unanswered_applications) > 0:
                link = url_path(settings.MCM_BASE_LINK, 'dashboard/application')
                content += "[Unanswered Applications: {}]({})".format(len(unanswered_applications), link)
            unclaimed_tickets = Ticket.objects.filter(staff=None, resolved=False)
            if len(unclaimed_tickets) > 0:
                link = url_path(settings.MCM_BASE_LINK, 'dashboard/ticket')
                if content:
                    content += "\n\n"
                content += "[Unclaimed Tickets: {}]({})".format(len(unclaimed_tickets), link)
            if content:
                embed.title = "MCM Reminder"
                embed.description = content
                await self.discord_message(channel, embed)

    async def on_disconnect(self):
        global discord_status
        discord_status = DiscordStatus.STOPPED

    async def discord_message(self, dest, message):
        if isinstance(message, discord.Embed):
            for idx, field in enumerate(message.fields):
                if not field.value:
                    message.set_field_at(idx, name=field.name, value="N/A")
            await dest.send(embed=message)
        else:
            await dest.send(message)

    async def on_command_error(self, context, exception):
        if not isinstance(exception, commands.CommandInvokeError):
            return
        if hasattr(exception, "original"):
            error = ''.join(traceback.format_tb(exception.original.__traceback__))
        else:
            error = exception
        logger.error(error)
        for user_id in self.error_users:
            user = self.get_user(user_id)
            if user:
                await self.discord_message(user, '```python\n{}```'.format(error))

    def run_bot(self):
        global discord_loop
        try:
            discord_loop.run_until_complete(self.start(self.token))
        except KeyboardInterrupt:
            logger.info("Bot received keyboard interrupt")
        except Exception as e:
            print(e)
            logger.info('Bot encountered the following unhandled exception %s', e)


def start():
    global discord_loop, discord_bot, discord_status
    if discord_status != DiscordStatus.STOPPED:
        return
    token = getattr(settings, 'DISCORD_BOT_TOKEN', None)
    discord_loop = asyncio.new_event_loop()
    discord_bot = Discord(token, discord_loop)
    thread = threading.Thread(target=discord_bot.run_bot)
    thread.start()
    discord_status = DiscordStatus.STARTING


def stop():
    global discord_loop, discord_bot, discord_status
    if discord_status == DiscordStatus.STARTED:
        discord_loop.create_task(discord_bot.close())
        discord_status = DiscordStatus.STOPPING
        discord_loop = None
        discord_bot = None


def restart():
    def _restart():
        stop()
        while discord_status.is_running():
            pass
        start()
    if discord_status != DiscordStatus.STARTED:
        return
    thread = threading.Thread(target=_restart)
    thread.start()


def status():
    return discord_status.is_running()


def display():
    return str(discord_status)


class DestType(Enum):
    CHANNEL = 1
    USER = 2


def send(dest_type: DestType, dest_id: int, message: str = "", embed: discord.Embed = None):
    async def _send():
        if dest_type == DestType.CHANNEL:
            dest = discord_bot.get_channel(dest_id)
        elif dest_type == DestType.USER:
            dest = discord_bot.get_user(dest_id)
        else:
            return
        if message is not None:
            await dest.send(message)
        if embed is not None:
            for idx, field in enumerate(embed.fields):
                if not field.value:
                    embed.set_field_at(idx, name=field.name, value="N/A")
            await dest.send(embed=embed)
    global discord_loop, discord_bot
    if discord_loop:
        discord_loop.create_task(_send())
