import discord
from discord import app_commands
import yt_dlp as youtube_dl
import logging
import asyncio


class Music:
    def __init__(self, bot):
        self.bot = bot
        self.logger = logging.getLogger('Music')
        self.logger.setLevel(logging.DEBUG)
        handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
        handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
        self.logger.addHandler(handler)
        self.ydl_opts = {
            'format': 'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
            'quiet': True
        }
        self.volume = 0.25  # Default volume (25%)

    async def search_youtube(self, query):
        with youtube_dl.YoutubeDL(self.ydl_opts) as ydl:
            try:
                requests = ydl.extract_info(f"ytsearch:{query}", download=False)
                return requests['entries'][0]
            except Exception as e:
                self.logger.error(f'Error searching YouTube: {e}')
                return None

    async def join(self, interaction: discord.Interaction):
        self.logger.debug(f'User {interaction.user} is attempting to join a voice channel')

        if interaction.guild.voice_client:
            await interaction.followup.send(embed=discord.Embed(description="Already connected to a voice channel.", color=discord.Color.red()))
            return

        if interaction.user.voice:
            channel = interaction.user.voice.channel
            try:
                voice_client = await channel.connect()
                await voice_client.guild.change_voice_state(channel=channel, self_deaf=True)
                await interaction.followup.send(embed=discord.Embed(description=f"Joined {channel.name}", color=discord.Color.green()))
                self.logger.info(f"Successfully connected to {channel.name}")
            except discord.ClientException as e:
                self.logger.error(f'Error joining voice channel: {e}')
                await interaction.followup.send(embed=discord.Embed(description=f"Error joining voice channel: {e}", color=discord.Color.red()))
            except asyncio.TimeoutError:
                self.logger.error('Timeout error while trying to connect to voice channel')
                await interaction.followup.send(embed=discord.Embed(description='Timeout error while trying to connect to voice channel', color=discord.Color.red()))
            except Exception as e:
                self.logger.error(f'Unexpected error: {e}')
                await interaction.followup.send(embed=discord.Embed(description=f'Unexpected error: {e}', color=discord.Color.red()))
        else:
            await interaction.followup.send(embed=discord.Embed(description="You're not in a voice channel.", color=discord.Color.red()))

    async def leave(self, interaction: discord.Interaction):
        self.logger.debug(f'User {interaction.user} is attempting to leave the voice channel')

        if interaction.guild.voice_client:
            await interaction.guild.voice_client.disconnect()
            await interaction.followup.send(embed=discord.Embed(description="Left the voice channel.", color=discord.Color.green()))
            self.logger.info(f"Disconnected from the voice channel in {interaction.guild.name}")
        else:
            await interaction.followup.send(embed=discord.Embed(description="I'm not in a voice channel.", color=discord.Color.red()))

    async def play(self, interaction: discord.Interaction, search: str):
        self.logger.debug(f'User {interaction.user} is attempting to play: {search}')

        if not interaction.guild.voice_client:
            await self.join(interaction)
            if not interaction.guild.voice_client:
                return

        info = await self.search_youtube(search)
        if info:
            url = info['url']
            title = info.get('title')
            uploader = info.get('uploader', 'Unknown')
            duration = info.get('duration', 0)
            thumbnail = info.get('thumbnail', '')
            self.logger.debug(f'Playing URL: {url}')
            try:
                ffmpeg_options = {
                    'options': f'-vn -filter:a "volume={self.volume}"'
                }
                source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **ffmpeg_options), volume=self.volume)
                interaction.guild.voice_client.play(source)
                embed = discord.Embed(title=title, description=f"Uploader: {uploader}\nDuration: {duration // 60}:{duration % 60:02d}", color=discord.Color.green())
                embed.set_thumbnail(url=thumbnail)
                await interaction.followup.send(embed=embed)
                self.logger.info(f'Now playing: {title}')
            except Exception as e:
                self.logger.error(f'Error playing audio: {e}')
                await interaction.followup.send(embed=discord.Embed(description='Error playing the audio.', color=discord.Color.red()))
        else:
            await interaction.followup.send(embed=discord.Embed(description='Could not find any results.', color=discord.Color.red()))
            self.logger.error('Could not find any results for the search query')

    async def pause(self, interaction: discord.Interaction):
        self.logger.debug(f'User {interaction.user} is attempting to pause the music')

        if interaction.guild.voice_client and interaction.guild.voice_client.is_playing():
            interaction.guild.voice_client.pause()
            await interaction.followup.send(embed=discord.Embed(description="Paused the current song.", color=discord.Color.green()))
            self.logger.info('Paused the current song')
        else:
            await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))

    async def resume(self, interaction: discord.Interaction):
        self.logger.debug(f'User {interaction.user} is attempting to resume the music')

        if interaction.guild.voice_client and interaction.guild.voice_client.is_paused():
            interaction.guild.voice_client.resume()
            await interaction.followup.send(embed=discord.Embed(description="Resumed the paused song.", color=discord.Color.green()))
            self.logger.info('Resumed the paused song')
        else:
            await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))

    async def stop(self, interaction: discord.Interaction):
        self.logger.debug(f'User {interaction.user} is attempting to stop the music')

        if interaction.guild.voice_client and interaction.guild.voice_client.is_playing():
            interaction.guild.voice_client.stop()
            await interaction.followup.send(embed=discord.Embed(description="Stopped the current song.", color=discord.Color.green()))
            self.logger.info('Stopped the current song')
        else:
            await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))

    async def set_volume(self, interaction: discord.Interaction, volume: float):
        self.logger.debug(f'User {interaction.user} is attempting to set volume to {volume}')

        if 0 <= volume <= 1:
            self.volume = volume
            if interaction.guild.voice_client and interaction.guild.voice_client.source:
                interaction.guild.voice_client.source.volume = volume
            await interaction.followup.send(embed=discord.Embed(description=f"Volume set to {volume*100:.0f}%", color=discord.Color.green()))
            self.logger.info(f'Volume set to {volume*100:.0f}%')
        else:
            await interaction.followup.send(embed=discord.Embed(description="Volume must be between 0 and 1.", color=discord.Color.red()))
            self.logger.error('Invalid volume level attempted')

    def setup(self, tree: app_commands.CommandTree):
        @tree.command(name="join", description="Join the voice channel")
        async def join_command(interaction: discord.Interaction):
            await interaction.response.defer()  # Defer the interaction response
            await self.join(interaction)

        @tree.command(name="leave", description="Leave the voice channel")
        async def leave_command(interaction: discord.Interaction):
            await interaction.response.defer()  # Defer the interaction response
            await self.leave(interaction)

        @tree.command(name="play", description="Play a song from YouTube")
        async def play_command(interaction: discord.Interaction, search: str):
            await interaction.response.defer()  # Defer the interaction response
            await self.play(interaction, search)

        @tree.command(name="pause", description="Pause the current song")
        async def pause_command(interaction: discord.Interaction):
            await interaction.response.defer()  # Defer the interaction response
            await self.pause(interaction)

        @tree.command(name="resume", description="Resume the paused song")
        async def resume_command(interaction: discord.Interaction):
            await interaction.response.defer()  # Defer the interaction response
            await self.resume(interaction)

        @tree.command(name="stop", description="Stop the current song")
        async def stop_command(interaction: discord.Interaction):
            await interaction.response.defer()  # Defer the interaction response
            await self.stop(interaction)

        @tree.command(name="volume", description="Set the volume (0 to 1)")
        async def volume_command(interaction: discord.Interaction, volume: float):
            await interaction.response.defer()  # Defer the interaction response
            await self.set_volume(interaction, volume)


def setup(bot):
    music = Music(bot)
    music.setup(bot.tree)