import discord
from lytils import cprint


class DiscordBot:

    def __init__(self, token: str, intents, debug: bool = False):
        """
        @token : string - token representing current bot generated by Discord
        @intents : Intents - object representing Discord permissions
            message_content set to False by default
        @debug : boolean - whether to display debug statements or not
        """
        self.set_client(intents)
        self.__token = token
        self.__debug = debug
        self.on_ready = None
        self.on_message = None

    def set_client(self, intents):
        self.__client = discord.Client(intents=intents)
    
    def get_client(self):
        return self.__client
    
    def get_channel(self, server_id, channel_name: str):
        # Get server by id
        server = discord.utils.get(self.__client.guilds, id=server_id)

        if server:
            # Find the channel by name
            channel = discord.utils.get(server.channels, name=channel_name)

            if channel:
                return channel
            else:
                cprint(f'<y>Channel \'{channel_name}\' not found in \'{server.name}\'.')
        else:
            cprint(f'<y>Server Id \'{server_id}\' not found.')

        return None


    def set_on_ready(self):
        # To customize on_ready method, set bot.on_ready to async def
        @self.__client.event
        async def on_ready():
            cprint(f'<g>{self.__client.user} is now running!')
            if self.on_ready is not None:
                await self.on_ready()

    def set_on_message(self):
        # To customize on_message method, set bot.on_message to async def
        @self.__client.event
        async def on_message(message):
            if message.author == self.__client.user:
                # This is to prevent the bot from responding to itself.
                return

            user = str(message.author)
            user_message = str(message.content)
            channel = str(message.channel)
            if self.__debug:
                cprint(f'<y>{user} said: "{user_message}" ({channel})')

            if self.on_message is not None:
                await self.on_message(message)
            else:
                if user_message[0] == '?':
                    # '?' is default for requesting a private response I guess.
                    user_message = user_message[1:]
                    await self.send_private_message(message, user_message)
                else:
                    await self.send_message(message, user_message)

    def get_response(message: str) -> str:
        # To customize get_response method, set bot.get_response to func
        # Lowercase message to handle case insensitive text
        p_message = message.lower()

        if p_message == 'hello':
            return 'Hey there!'

        if p_message == '!help':
            return '`This is a help message that you can modify.`'

    async def send_message(self, message, user_message: str):
        # Await this within your custom on_message function
        # Respond to channel which message came from
        try:
            response = DiscordBot.get_response(user_message)
            await message.channel.send(response)
        except Exception as e:
            cprint(f'<r>Error: {e}')

    async def send_private_message(self, message, user_message: str):
        # Await this within your custom on_message function
        # Respond privately to sender which message came from
        try:
            response = DiscordBot.get_response(user_message)
            await message.author.send(response)
        except Exception as e:
            cprint(f'<r>Error: {e}')

    async def send_message_to_channel(
            self,
            server_id,
            channel_name: str,
            message: str
        ):
        # Await this within your custom on_message function
        # Respond to channel which message came from
        try:
            # Get the 'norcal' text channel in the current server
            channel = self.get_channel(server_id, channel_name)
            await channel.send(message)
        except Exception as e:
            cprint(f'<r>Error: {e}')

    def run(self):
        self.set_on_ready()
        self.set_on_message()
        self.__client.run(self.__token)

    async def close(self):
        await self.__client.close()
