# Imports
import os
from discord.webhook.async_ import interaction_message_response_params
import requests
import discord
from discord.ext import commands
from discord import DiscordException, NotFound, app_commands
from dotenv import load_dotenv


# Environment vars
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
JELLYFIN_URL = os.getenv("JELLYFIN_URL")
JELLYFIN_API_KEY = os.getenv("JELLYFIN_API_KEY")


# Constants
PLAY_QUERY_LIMIT = 1
SEARCH_QUERY_LIMIT = 5


# Setting the opus path, since Linux can't read it

# Request headers
headers= {
    "X-Emby-Token": JELLYFIN_API_KEY
}

# discord bot setups
intents = discord.Intents.default()
intents.message_content = True
intents.voice_states = True



# ----- Classes ----- #
# TODO: turn this in to an import in a separate file
class JellyfinBot(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix="/", intents=intents)

    async def setup_hook(self):
        await self.tree.sync()

bot = JellyfinBot()
@bot.event
async def on_ready():
    print(f"🪼 Logged in as {bot.user}")

# ----- Helpful Structures ----- #
guild_queue_dict = dict()


# ----- Helper Functions ----- #

# makeRequest:
# makes a request to the jellyfin server.
# Returns an error string or

# begin makeRequest -----
async def make_request(url: str, params: dict, interaction: discord.Interaction) -> dict:
    # Default return value
    data = dict()

    try:
        # Makes the request
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()

        # Presets the default value for data if this doesn't work
        data = response.json().get("Items", [])
        return data
    
    except requests.RequestException as e:
        await interaction.followup.send("⚠️ Failed to contact Jellyfin server.")
        print(f"Error: {e}")
        return data
# end makeRequest -----

# channelPlay
# does logic for songs in a voice channel

# ----- Commands ----- #

# Play a song

# Play function
# Plays a song with the given title in wherever the user is

# start play -----

@bot.tree.command(name="play", description="Play a song from Jellyfin")
@app_commands.describe(title="Song title to play")
async def play(interaction: discord.Interaction, title: str):

    # Makes the reaction visible to everyone
    await interaction.response.defer()

    # Checks if not in a voice channel
    # Checks if in a voice channel
    try:
        # A better way of checking status
        voice_status = await interaction.user.fetch_voice()
        user_channel = voice_status.channel
        voice_client = interaction.guild.voice_client # Enables the voice feature for the bot

        # First check if it is already in a voice channel
        if voice_client.channel != user_channel:
            voice_client.disconnect()

        if voice_client.is_playing():
            voice_client.stop()

        # Finally, connect
        await user_channel.connect()
    except discord.errors.NotFound as e:
        print(f"Error: {e}")
        await interaction.followup.send("Nope! Not in a voice!")
        return
    except discord.errors.Forbidden as e:
        print(f"Error: {e}")
        await interaction.followup.send("Not allowed in here!")
        return


    url = f"{JELLYFIN_URL}/Items"
    params = {
        "searchTerm": title,
        "Recursive": True,
        "IncludeItemTypes": "Audio",
        "Limit": PLAY_QUERY_LIMIT
    }

    # makes a request to the server, and returns the data it acquires
    data = await make_request(url, params, interaction)
    if not data:
        await interaction.followup.send(f"❌ No song found matching `{title}`.")
        return

    # Finds the item, and displays information about the location
    print(f"Found {len(data)} items matching `{title}`.")

    # Deconstructing the dict 'data'
    query_song = data[0] 
    query_song_id = query_song.get('Id') 
    query_song_title = query_song.get('Name')
    query_song_artist = query_song.get('AlbumArtist', ['Unknown Artist'])

    
    # First, we make a dictionary that has a list inside.
    # Then, we get the name of the guild and put that in the dictionary
    # then AFTER, we put the song inside the list that the guild points to
    # on the play command, we add that song to the list

    # Check if the guild is not known guilds
    if guild_queue_dict.get(interaction.guild_id) == None:
        # add it to the playlist dict
        guild_queue_dict[interaction.guild_id] = list()

    guild_queue_dict[interaction.guild_id].append((query_song_id, query_song_title, query_song_artist))

    # If someone is already playing something, we just add to their queue.
    # Probably a better way of piggybacking
    if voice_client.is_playing():
        await interaction.followup.send(f"Queued: **{query_song_title}** by *{query_song_artist}*")
        return

    headers_str = f"-headers \"X-Emby-Token: {JELLYFIN_API_KEY}\""
    for song in guild_queue_dict[interaction.guild_id]:
        try:
            # Song information from tuple
            song_id = song[0] 
            song_title = song[1]
            song_artist = song[2]

            audio_stream_url = f"{JELLYFIN_URL}/Audio/{song_id}/stream?static=True"
            source = discord.FFmpegOpusAudio(audio_stream_url,
                                            before_options=f'{headers_str} -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
                                            options="-vn")
            # Play the grabbed audio file
            voice_client.play(source, after=lambda e: print(f"Finished playing: {e}"))
            await interaction.followup.send(f"Now playing: **{song_title}** by *{song_artist}*")

            #Cheap way of holding the loop until its done
            while voice_client.is_playing():
                continue

            # Then, we get rid of this queued item
            guild_queue_dict[interaction.guild_id].pop(0)

        except discord.ClientException as e:
            await interaction.followup.send("Unable to decode your song!")
            print(f"Potential FFMPEG/OPUS decoding error: {e}")
# end play -----


# Search command
# searches for a song

# Search function
# start search -----
@bot.tree.command(name="search", description="Search for a song on Jellyfin")
@app_commands.describe(title="Song title to search for")
async def search(interaction: discord.Interaction, title: str):
    await interaction.response.defer()

    # Builds the url
    url = f"{JELLYFIN_URL}/Items"
    params = {
        "searchTerm": title,
        "Recursive": True,
        "IncludeItemTypes": "Audio",
        "Limit": SEARCH_QUERY_LIMIT
    }

    # Get the data
    data = await make_request(url, params, interaction)

    if not data:
        await interaction.followup.send(f"No song found matching `{title}`.")
        return
   
    lines = []

    for song in data:
        title = song.get("Name")
        artist = song.get("AlbumArtist", ["Unknown Artist"])
        lines.append(f"**{title}** by *{artist}*")
    await interaction.followup.send("\n".join(lines))
# end search -----


# Disconnect command!
# Disconnects from the voice channel
@bot.tree.command(name="disconnect", description="Disconnects from current voice channel!")
async def disconnect(interaction: discord.Interaction):
    # Checks if not in a voice channel
    if interaction.guild.voice_client == None:
        await interaction.followup.send("I am not in any voice channel!")
        return
    # disconnects with a helpful message
    await interaction.guild.voice_client.disconnect()
    await interaction.followup.send("Disconnected!")
    return

# Queues
# Enqueue an item




# ----- FINALLY ----- #
bot.run(TOKEN)
