import discord
from discord.ext import commands
from discord import app_commands

from config_manager import ConfigManager
import cogs.views as views
from enum import Enum

import storybot_exceptions
import re

class GuildSettings(Enum):
    AuthorRole=7
    AnnouncementChannel=1
    StoryOutputChannel=2
    
    SafeModeEnabled=5
    
    TimeoutDays=3
    TimeoutsBeforeAutoPause=8
    ShowAuthorAlongWithStory=6
    
    MaxAdditionLength=9

# Listens for DMs to add to the story
class configure_cog(commands.Cog):
    discord_generic = None
    
    def __init__(self, bot: discord.Client):
        self.bot = bot
        self.config_manager : ConfigManager = bot.config_manager
        self.file_manager = bot.file_manager
            
    @app_commands.guild_only()
    @app_commands.command(name="configure")
    @app_commands.checks.has_permissions(moderate_members=True)
    async def configure(self, interaction: discord.Interaction, setting: GuildSettings, value: str = None, public: bool = True):
        """Admin command: change some of the bot's configuration

        Args:
            interaction (discord.Interaction): the default interaction
            setting (GuildSettings): the setting to change
            value (str, optional): the new value for the setting
            public (bool, optional): Whether the response should be public. Defaults to True.

        Raises:
            ValueError: I'm not too sure where this comes from, but it was auto-generated by the docs
        """
        
        yes_options = ["1", "yes", "true", "yep", "duh", "yes, please", "affirmative", "+"]
        no_options = ["0", "no", "nah", "false", "no, thank you", "negative", "-"]

        match setting:
            case GuildSettings.StoryOutputChannel:
                await self.configure_story_output_channel(interaction=interaction, channel=value, public=public)
               
            case GuildSettings.AnnouncementChannel:
                await self.configure_story_announcement_channel(interaction=interaction, channel=value, public=public)
            
            case GuildSettings.TimeoutDays:
                try:
                    new_int_value=int(value)
                except ValueError:
                    # They didn't enter a number
                    await self.discord_generic.reply_to_message(content=f"Please enter a valid integer (whole number) for the `value`!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                except TypeError:
                    # They didn't enter anything
                    await self.discord_generic.reply_to_message(content=f"Please enter a number into the `value` field!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_timeout_days(interaction=interaction, new_timeout_days=new_int_value, public=public)
            
            case GuildSettings.TimeoutsBeforeAutoPause:
                try:
                    new_int_value=int(value)
                except ValueError:
                    # They didn't enter a number
                    await self.discord_generic.reply_to_message(content=f"Please enter a valid integer (whole number) for the `value`!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                except TypeError:
                    # They didn't enter anything
                    await self.discord_generic.reply_to_message(content=f"Please enter a number into the `value` field!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_timeouts_before_pause(interaction=interaction, timeouts_before_pause=new_int_value, public=public)
                
            case GuildSettings.MaxAdditionLength:
                try:
                    new_int_value=int(value)
                except ValueError:
                    # They didn't enter a number
                    await self.discord_generic.reply_to_message(content=f"Please enter a valid integer (whole number) for the `value`!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                except TypeError:
                    # They didn't enter anything
                    await self.discord_generic.reply_to_message(content=f"Please enter a number into the `value` field!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_max_message_length(interaction=interaction, max_message_length=new_int_value, public=public)
            
            case GuildSettings.SafeModeEnabled:
                
                if value == None:
                    await self.discord_generic.reply_to_message(content="To toggle safe mode, please set the `value` of this command to either \"yes\" or \"no\". Thank you!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                value=value.lower()
                
                if value in yes_options:
                    toggle = True
                elif value in no_options:
                    toggle = False
                else:
                    await self.discord_generic.reply_to_message(content=f"I couldn't understand that. Please try again, and this time, set the `value` to \"yes\" or \"no\". Thanks!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_safe_mode_enabled(interaction=interaction, safe_mode_enabled=toggle, public=public)
                
            case GuildSettings.ShowAuthorAlongWithStory:
                

                if value == None:
                    await self.discord_generic.reply_to_message(content="To toggle this setting, please set the `value` of this command to either \"yes\" or \"no\". Thank you!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                value=value.lower()
                
                if value in yes_options:
                    toggle = True
                elif value in no_options:
                    toggle = False
                else:
                    await self.discord_generic.reply_to_message(content=f"I couldn't understand that. Please try again, and this time, set the `value` to \"yes\" or \"no\". Thanks!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_show_author_along_with_story(interaction=interaction, show_author_with_entry=toggle, public=public)
                
            case GuildSettings.AuthorRole:
                if value == None:
                    await self.discord_generic.reply_to_message(content="Please mention a valid role as the `value`!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                try:
                    role_id = int(re.sub(r'[^0-9]', '', value))
                    if not (10000000000000000 <= role_id <= 18446744073709551615): # Make sure that the role id is valid (between `10^17` and `(2^64)-1`)
                        raise ValueError()
                except ValueError:
                    await self.discord_generic.reply_to_message(content="This is not a valid role id!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                role = interaction.guild.get_role(role_id)
                if role == None:
                    await self.discord_generic.reply_to_message(content="I couldn't find that role!", reply_to=interaction, error=True, ephemeral=not public)
                    return
                
                await self.configure_author_role(interaction=interaction, role=role, public=public)
                
                
    
    async def configure_story_output_channel(self, interaction: discord.Interaction, channel: str = None, public: bool = True):
        """Choose a channel for the bot to output the story to

        Args:
            interaction (discord.Interaction): The default interaction
            channel (str, optional): The channel to mirror the story to
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        if channel == None:
            new_story_output_channel = await self.discord_generic.choose_a_channel_in_a_server(guild_id=interaction.guild_id, interaction=interaction, message="Choose a channel to output the story in!")
            if new_story_output_channel == None: return
            
        else:
            new_story_output_channel = re.sub(r'[^0-9]', '', channel)
            if new_story_output_channel == '':
                return
            
            new_story_output_channel = int(new_story_output_channel)
            
            try:
                channel = await interaction.guild.fetch_channel(new_story_output_channel)
            except (discord.errors.Forbidden, discord.errors.NotFound):
                await self.discord_generic.reply_to_message(content=f"Hmm... I can't see that channel for some reason. Check my permissions, then try again.", reply_to=interaction, error=True, ephemeral=not public)
                return
            if not channel.permissions_for(await channel.guild.fetch_member(self.bot.user.id)).send_messages:
                await self.discord_generic.reply_to_message(content=f"I can see that channel, but I can't send messages in it. Check my permissions, then try again.", reply_to=interaction, error=True, ephemeral=not public)
                return
        
        if new_story_output_channel != 0: #If it is 0, that means cancel was pressed
            await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='story_output_channel', new_value=new_story_output_channel)
            
            if new_story_output_channel == -1:
                content=f"Disabled the Story Output Channel!"
            else:
                content=f"Finished changing the story output channel to <#{new_story_output_channel}>!"
            
            await self.discord_generic.reply_to_message(content=content, reply_to=interaction, ephemeral=not public)
    
    async def configure_story_announcement_channel(self, interaction: discord.Interaction, channel: str = None, public: bool = True):
        """Choose a channel for the bot to make announcements in

        Args:
            interaction (discord.Interaction): The default interaction
            channel (str, optional): The bot announcement channel
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        if channel == None:
            new_story_announcement_channel = await self.discord_generic.choose_a_channel_in_a_server(guild_id=interaction.guild_id, interaction=interaction, message="Choose a channel to announce the current user in!")
            if new_story_announcement_channel == None: return
            
        else:
            new_story_announcement_channel = re.sub(r'[^0-9]', '', channel)
            if new_story_announcement_channel == '':
                return
            
            new_story_announcement_channel = int(new_story_announcement_channel)
            
            try:
                channel = await interaction.guild.fetch_channel(new_story_announcement_channel)
            except (discord.errors.Forbidden, discord.errors.NotFound):
                await self.discord_generic.reply_to_message(content=f"Hmm... I can't see that channel for some reason. Check my permissions, then try again.", reply_to=interaction, error=True, ephemeral=not public)
                return
            if not channel.permissions_for(await channel.guild.fetch_member(self.bot.user.id)).send_messages:
                await self.discord_generic.reply_to_message(content=f"I can see that channel, but I can't send messages in it. Check my permissions, then try again.", reply_to=interaction, error=True, ephemeral=not public)
                return
        
        
        if new_story_announcement_channel != 0: #If it is 0, that means cancel was pressed
            await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='story_announcement_channel', new_value=new_story_announcement_channel)
            
            if new_story_announcement_channel == -1:
                content=f"Disabled the Story Announcement Channel!"
            else:
                content=f"Finished changing the story announcement channel to <#{new_story_announcement_channel}>!"
            
            await self.discord_generic.reply_to_message(content=content, reply_to=interaction, ephemeral=not public)
    
    async def configure_timeout_days(self, interaction: discord.Interaction, new_timeout_days: int, public: bool = True):
        """Choose how many days each author has to write before they're skipped

        Args:
            interaction (discord.Interaction): The default interaction
            new_timeout_days (int): How many days authors have to write
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        max_int_value = await self.config_manager.get_max_timeout_days_configurable()
                
        if new_timeout_days <= 0:
            await self.discord_generic.reply_to_message(content=f"Please choose a number of days greater than 0!", reply_to=interaction, error=True, ephemeral=not public)
            return
        elif max_int_value > 0 and new_timeout_days > max_int_value:
            await self.discord_generic.reply_to_message(content=f"Timeout lengths greater than {max_int_value} are not supported by the bot right now. If you would like a longer timeout length, join my support server and let me know!", reply_to=interaction, error=True, ephemeral=not public)
            return
        
        await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='timeout_days', new_value=new_timeout_days)
        await self.discord_generic.reply_to_message(content=f"Finished setting the timeout days to **{new_timeout_days}**!", reply_to=interaction, ephemeral=not public)
    
    async def configure_timeouts_before_pause(self, interaction: discord.Interaction, timeouts_before_pause: int, public: bool = True):
        """Choose how many times a user can time out before they're paused in this server

        Args:
            interaction (discord.Interaction): The default interaction
            timeouts_before_pause (int): How many times a user can time out
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        min_int_value = 3
        max_int_value = 30
        
        if timeouts_before_pause < min_int_value:
            await self.discord_generic.reply_to_message(content=f"Please choose a number of timeouts greater than {min_int_value - 1}!", reply_to=interaction, error=True, ephemeral=not public)
            return
        elif timeouts_before_pause > max_int_value:
            await self.discord_generic.reply_to_message(content=f"Having more than {max_int_value} timeouts before you're automatically paused is not supported by the bot right now. If you would like this to be larger, join my support server and let me know!", reply_to=interaction, error=True, ephemeral=not public)
            return
        
        await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='timeouts_before_pause', new_value=timeouts_before_pause)
        await self.discord_generic.reply_to_message(content=f"Finished setting the number of timeouts before you're paused to **{timeouts_before_pause}**!", reply_to=interaction, ephemeral=not public)
        
    async def configure_max_message_length(self, interaction: discord.Interaction, max_message_length: int, public: bool = True):
        """Choose the maximum length each user can write

        Args:
            interaction (discord.Interaction): The default interaction
            max_message_length (int): The number of characters. Set this to 0 to disable it
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        min_int_value = 200
        max_int_value = 4000
        
        
        if max_message_length == 0:
            max_message_length = None
        elif max_message_length < min_int_value:
            await self.discord_generic.reply_to_message(content=f"The minimum addition length limit is {min_int_value} characters!", reply_to=interaction, error=True, ephemeral=not public)
            return
        elif max_message_length > max_int_value:
            await self.discord_generic.reply_to_message(content=f"The maximum addition length limit is {max_int_value} characters!", reply_to=interaction, error=True, ephemeral=not public)
            return
        
        await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='max_message_length', new_value=max_message_length)
        await self.discord_generic.reply_to_message(content=f"Finished setting the maximum size of an addition to **{max_message_length}**!", reply_to=interaction, ephemeral=not public)
    
    async def configure_safe_mode_enabled(self, interaction: discord.Interaction, safe_mode_enabled: bool, public: bool = True):
        """Enables or disables Safe Mode for a server. This is an imperfect filter to help catch and block bad words and links in story submissions

        Args:
            interaction (discord.Interaction): The default interaction
            safe_mode_enabled (bool): Whether safe mode should be activated
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='safe_mode', new_value=safe_mode_enabled)
        
        if safe_mode_enabled:
            message = "Safe Mode has been enabled. While it is still imperfect, it will help filter out some vulgarity in messages. This may be improved on in the future."
        else:
            message = "**Warning: Safe Mode has been disabled**. Messages will *not* be filtered for vulgarity and the like."
            
        await self.discord_generic.reply_to_message(content=message, reply_to=interaction, ephemeral=not public)
    
    async def configure_show_author_along_with_story(self, interaction: discord.Interaction, show_author_with_entry: bool, public: bool = True):
        """Determines whether author names should be shown in the story output channel

        Args:
            interaction (discord.Interaction): The default interaction
            show_author_with_entry (bool): Whether author names should be shown
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        await self.file_manager.set_config_value(interaction.guild_id, XSS_WARNING_config_name='embeds', new_value=show_author_with_entry)
        
        if show_author_with_entry:
            message = "Success! Now, authors will be shown alongside their additions to the story."
        else:
            message = "Success! Now, authors will no longer be shown alongside their additions to the story."
            
        await self.discord_generic.reply_to_message(content=message, reply_to=interaction, ephemeral=not public)
    
    async def configure_author_role(self, interaction: discord.Interaction, role: discord.Role, public: bool = True):
        """Choose a role to be automatically given/removed from authors in this server

        Args:
            interaction (discord.Interaction): The default interaction
            role (discord.Role): The role to give story authors
            public (bool, optional): Whether the response should be public. Defaults to True
        """
        await interaction.response.defer()
        
        if not interaction.guild.me.guild_permissions.manage_roles:
            await self.discord_generic.reply_to_message(content="This bot doesn't have the proper permissions! You may need to re-invite the bot and make sure it has the `Manage Roles` permission!", reply_to=interaction, error=True, ephemeral=not public, view=views.InviteAndSupportServerLinks())
            return
        
        try:
            await self.discord_generic.add_role_to_all_authors(guild=interaction.guild, role=role)
            await self.file_manager.set_author_role_id(guild_id=interaction.guild_id, role_id=role.id)
            
            await self.discord_generic.reply_to_message(content=f"The author role has been set to {role.mention}!", reply_to=interaction, ephemeral=not public)
        except storybot_exceptions.MissingPermissionsException:
            await self.discord_generic.reply_to_message(content=f"This bot is missing the manage_roles permission, which it needs to assign this role to authors. Click the link below to re-add the bot to this server with the required permissions!", reply_to=interaction, ephemeral=not public, error=True, view=views.InviteAndSupportServerLinks())
        except discord.errors.Forbidden:
            await self.discord_generic.reply_to_message(content=f"I don't have permission to manage the {role.mention} role. This probably means that you need to move this bot's role above that role in your server's Manage Roles menu.\n\nIf you want to use this as the author role, you'll need to run `/configure setting:AuthorRole value:{role.mention}` after fixing the bot's permissions.", reply_to=interaction, ephemeral=not public, error=True, view=views.InviteAndSupportServerLinks())
    

async def setup(bot):
    await bot.add_cog(configure_cog(bot))

