import asyncio

import discord
from sqlitedict import SqliteDict

from services.environment_service import EnvService
from services.moderations_service import Moderation, ThresholdSet

MOD_DB = None
try:
    print("Attempting to retrieve the General and Moderations DB")
    MOD_DB = SqliteDict("main_db.sqlite", tablename="moderations", autocommit=True)
except Exception as e:
    print("Failed to retrieve the General and Moderations DB")
    raise e


class ModerationsService(discord.Cog, name="ModerationsService"):
    """Cog containing moderation tools and features"""

    def __init__(
        self,
        bot,
        usage_service,
        model,
    ):
        super().__init__()
        self.bot = bot
        self.usage_service = usage_service
        self.model = model

        # Moderation service data
        self.moderation_queues = {}
        self.moderation_alerts_channel = EnvService.get_moderations_alert_channel()
        self.moderation_enabled_guilds = []
        self.moderation_tasks = {}
        self.moderations_launched = []

        # Defaults
        self.default_warn_set = ThresholdSet(0.01, 0.05, 0.05, 0.91, 0.1, 0.45, 0.1)
        self.default_delete_set = ThresholdSet(0.26, 0.26, 0.1, 0.95, 0.03, 0.85, 0.4)

    @discord.Cog.listener()
    async def on_ready(self):
        """Check moderation service for each guild"""
        for guild in self.bot.guilds:
            self.get_or_set_warn_set(guild.id)
            self.get_or_set_delete_set(guild.id)
            await self.check_and_launch_moderations(guild.id)
        print("The moderation service is ready.")

    def check_guild_moderated(self, guild_id):
        """Given guild id, return bool of moderation status"""
        return guild_id in MOD_DB and MOD_DB[guild_id]["moderated"]

    def get_moderated_alert_channel(self, guild_id):
        """Given guild id, return alert channel"""
        return MOD_DB[guild_id]["alert_channel"]

    def set_moderated_alert_channel(self, guild_id, channel_id):
        """Given guild id and channel id, set channel to recieve alerts"""
        MOD_DB[guild_id] = {"moderated": True, "alert_channel": channel_id}
        MOD_DB.commit()

    def get_or_set_warn_set(self, guild_id):
        """Get warn_set set for the guild, if not set them from default values"""
        guild_id = str(guild_id)
        key = guild_id + "_warn_set"
        if key not in MOD_DB:
            MOD_DB[key] = zip(
                self.default_warn_set.keys, self.default_warn_set.thresholds
            )
            MOD_DB.commit()
        return dict(MOD_DB[key])

    def get_or_set_delete_set(self, guild_id):
        """Get delete_set set for the guild, if not set them from default values"""
        guild_id = str(guild_id)
        key = guild_id + "_delete_set"
        if key not in MOD_DB:
            MOD_DB[key] = zip(
                self.default_delete_set.keys, self.default_delete_set.thresholds
            )
            MOD_DB.commit()
        return dict(MOD_DB[key])

    def set_warn_set(self, guild_id, threshold_set):
        """Set threshold for warning a message"""
        guild_id = str(guild_id)
        key = guild_id + "_warn_set"
        MOD_DB[key] = zip(threshold_set.keys, threshold_set.thresholds)
        MOD_DB.commit()

    def set_delete_set(self, guild_id, threshold_set):
        """Set threshold for deleting a message"""
        guild_id = str(guild_id)
        key = guild_id + "_delete_set"
        MOD_DB[key] = zip(threshold_set.keys, threshold_set.thresholds)
        MOD_DB.commit()

    def set_guild_moderated(self, guild_id, status=True):
        """Set the guild to moderated or not"""
        if guild_id not in MOD_DB:
            MOD_DB[guild_id] = {"moderated": status, "alert_channel": 0}
            MOD_DB.commit()
            return
        MOD_DB[guild_id] = {
            "moderated": status,
            "alert_channel": self.get_moderated_alert_channel(guild_id),
        }
        MOD_DB.commit()

    async def check_and_launch_moderations(self, guild_id, alert_channel_override=None):
        """Create the moderation service"""
        if self.check_guild_moderated(guild_id):
            Moderation.moderation_queues[guild_id] = asyncio.Queue()

            moderations_channel = await self.bot.fetch_channel(
                self.get_moderated_alert_channel(guild_id)
                if not alert_channel_override
                else alert_channel_override
            )
            warn_set_nums = self.get_or_set_warn_set(guild_id).values()
            delete_set_nums = self.get_or_set_delete_set(guild_id).values()
            warn_set = ThresholdSet(*warn_set_nums)
            delete_set = ThresholdSet(*delete_set_nums)

            Moderation.moderation_tasks[guild_id] = asyncio.ensure_future(
                Moderation.process_moderation_queue(
                    Moderation.moderation_queues[guild_id],
                    0.25,
                    0.25,
                    moderations_channel,
                    warn_set,
                    delete_set,
                )
            )
            print("Launched the moderations service for guild " + str(guild_id))
            Moderation.moderations_launched.append(guild_id)
            return moderations_channel

        return None

    async def moderations_command(
        self, ctx: discord.ApplicationContext, status: str, alert_channel_id: str
    ):
        """command handler for toggling moderation and setting an alert channel"""
        await ctx.defer()

        try:
            if alert_channel_id:
                int(alert_channel_id)
        except ValueError:
            # the alert_channel_id was passed in as a channel NAME instead of an ID, fetch the ID.
            alert_channel = discord.utils.get(ctx.guild.channels, name=alert_channel_id)
            alert_channel_id = alert_channel.id

        if status == "on":
            # Check if the current guild is already in the database and if so, if the moderations is on
            if self.check_guild_moderated(ctx.guild_id):
                await ctx.respond("Moderations is already enabled for this guild")
                return

            # Create the moderations service.
            await self.start_moderations_service(
                guild_id=ctx.guild_id, alert_channel_id=alert_channel_id
            )
            await ctx.respond("Moderations is now enabled for this guild")

        elif status == "off":
            # Cancel the moderations service.
            await self.stop_moderations_service(ctx.guild_id)
            await ctx.respond(
                "Moderations is now disabled for this guild", ephemeral=True
            )

    async def stop_moderations_service(self, guild_id):
        """Remove guild moderation status and stop the service"""
        self.set_guild_moderated(guild_id, False)
        Moderation.moderation_tasks[guild_id].cancel()
        Moderation.moderation_tasks[guild_id] = None
        Moderation.moderation_queues[guild_id] = None
        Moderation.moderations_launched.remove(guild_id)

    async def start_moderations_service(self, guild_id, alert_channel_id=None):
        """Set guild moderation and start the service"""
        self.set_guild_moderated(guild_id)
        moderations_channel = await self.check_and_launch_moderations(
            guild_id,
            Moderation.moderation_alerts_channel
            if not alert_channel_id
            else alert_channel_id,
        )
        self.set_moderated_alert_channel(guild_id, moderations_channel.id)

    async def restart_moderations_service(self, ctx):
        """restarts the moderation of the guild it's run in"""
        if not self.check_guild_moderated(ctx.guild_id):
            await ctx.respond(
                "Moderations are not enabled, can't restart",
                ephemeral=True,
                delete_after=30,
            )
            return

        await ctx.respond(
            "The moderations service is being restarted...",
            ephemeral=True,
            delete_after=30,
        )
        await self.stop_moderations_service(ctx.guild_id)
        await ctx.send_followup(
            "The moderations service was stopped..", ephemeral=True, delete_after=30
        )
        await self.start_moderations_service(
            ctx.guild_id, self.get_moderated_alert_channel(ctx.guild_id)
        )
        await ctx.send_followup(
            "The moderations service was restarted successfully.",
            ephemeral=True,
            delete_after=30,
        )

    async def build_moderation_settings_embed(self, category, mod_set):
        embed = discord.Embed(
            title="Moderation Settings",
            description="The moderation settings for this guild for the type: "
            + category,
            color=discord.Color.yellow() if type == "warn" else discord.Color.red(),
        )

        # Add each key_value pair in the mod_set to the embed, make them fairly small
        for key, value in mod_set.items():
            embed.add_field(name=key, value=value, inline=False)

        return embed

    async def config_command(
        self,
        ctx: discord.ApplicationContext,
        config_type: str,
        hate,
        hate_threatening,
        self_harm,
        sexual,
        sexual_minors,
        violence,
        violence_graphic,
    ):
        """command handler for assigning threshold values for warn or delete"""
        all_args = [
            hate,
            hate_threatening,
            self_harm,
            sexual,
            sexual_minors,
            violence,
            violence_graphic,
        ]
        await ctx.defer(ephemeral=True)

        # Case for printing the current config
        if not any(all_args) and config_type != "reset":
            await ctx.respond(
                ephemeral=True,
                embed=await self.build_moderation_settings_embed(
                    config_type,
                    self.get_or_set_warn_set(ctx.guild_id)
                    if config_type == "warn"
                    else self.get_or_set_delete_set(ctx.guild_id),
                ),
            )
            return

        if config_type == "warn":
            # Check if no args were
            warn_set = self.get_or_set_warn_set(ctx.guild_id)

            new_warn_set = ThresholdSet(
                hate if hate else warn_set["hate"],
                hate_threatening if hate_threatening else warn_set["hate/threatening"],
                self_harm if self_harm else warn_set["self-harm"],
                sexual if sexual else warn_set["sexual"],
                sexual_minors if sexual_minors else warn_set["sexual/minors"],
                violence if violence else warn_set["violence"],
                violence_graphic if violence_graphic else warn_set["violence/graphic"],
            )
            self.set_warn_set(ctx.guild_id, new_warn_set)
            await self.restart_moderations_service(ctx)

        elif config_type == "delete":
            delete_set = self.get_or_set_delete_set(ctx.guild_id)

            new_delete_set = ThresholdSet(
                hate if hate else delete_set["hate"],
                hate_threatening
                if hate_threatening
                else delete_set["hate/threatening"],
                self_harm if self_harm else delete_set["self-harm"],
                sexual if sexual else delete_set["sexual"],
                sexual_minors if sexual_minors else delete_set["sexual/minors"],
                violence if violence else delete_set["violence"],
                violence_graphic
                if violence_graphic
                else delete_set["violence/graphic"],
            )
            self.set_delete_set(ctx.guild_id, new_delete_set)
            await self.restart_moderations_service(ctx)

        elif config_type == "reset":
            self.set_delete_set(ctx.guild_id, self.default_delete_set)
            self.set_warn_set(ctx.guild_id, self.default_warn_set)
            await self.restart_moderations_service(ctx)

    async def moderations_test_command(
        self, ctx: discord.ApplicationContext, prompt: str
    ):
        """command handler for checking moderation values of a given input"""
        await ctx.defer()
        response = await self.model.send_moderations_request(prompt)
        await ctx.respond(response["results"][0]["category_scores"])
        await ctx.send_followup(response["results"][0]["flagged"])
