use dialoguer::Select;
use dialoguer::theme::ColorfulTheme;
use modules::*;
use opentelemetry::KeyValue;
use opentelemetry::global;
use opentelemetry::trace::Status;
use opentelemetry::trace::TraceContextExt;
use opentelemetry::trace::Tracer;
use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
use songbird::Event;
use songbird::EventContext;
use songbird::SerenityInit;
use songbird::events::EventHandler as VoiceEventHandler;
use songbird::get;
use songbird::input::HlsRequest;
use songbird::input::Input;
use tracing::instrument;
use std::env;
use std::process::Command;
use std::process::Stdio;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::Layer;
use tracing_subscriber::filter::Targets;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

pub mod modules;
enum BotState {
    Stopped,
    Running,
}
struct TrackTraceHandler {
    otel_ctx: opentelemetry::Context,
    guild_id: String,
    track_url: String,
    hls_url: String,
    event_name: &'static str,
}

#[async_trait]
impl VoiceEventHandler for TrackTraceHandler {
    async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
        let tracer = global::tracer("RhythmWorks");
        let track_status = if let EventContext::Track(track_list) = ctx {
            track_list.first().map(|state| format!("{:?}", state.0))
        } else {
            None
        };
        tracer.in_span(self.event_name, |cx| {
            cx.span()
                .set_attribute(KeyValue::new("Guild_ID", self.guild_id.clone()));
            cx.span()
                .set_attribute(KeyValue::new("Youtube_URL", self.track_url.clone()));
            cx.span()
                .set_attribute(KeyValue::new("Stream_URL", self.hls_url.clone()));
            cx.span().add_event(
                self.event_name,
                vec![KeyValue::new(
                    "track_status",
                    track_status.unwrap().to_string(),
                )],
            );
        });

        None
    }
}
struct Handler;
fn get_user_voice_channel(ctx: &Context, msg: &Message) -> Option<ChannelId> {
    let guild_id = msg.guild_id?;
    let guild = ctx.cache.guild(guild_id)?;
    guild
        .voice_states
        .get(&msg.author.id)
        .and_then(|vs| vs.channel_id)
}

#[async_trait]
impl EventHandler for Handler {
    async fn message(&self, ctx: Context, msg: Message) {
        if msg.content == "!ping"
            && let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await
        {
            println!("Error sending message: {why:?}");
        }
        if msg.content == "!join" {
            let tracer = global::tracer("RhythmWorks");
            let span = tracer.start("Join Message Received");
            let cx = opentelemetry::Context::current_with_span(span);
            let guild_id = match msg.guild_id {
                Some(g) => {
                    tracing::info!("Received Guild ID");
                    cx.span().add_event(
                        "Received Guild ID",
                        vec![KeyValue::new("Server ID", g.to_string())],
                    );
                    cx.span().set_status(opentelemetry::trace::Status::Ok);
                    cx.span()
                        .set_attribute(KeyValue::new("Guild_ID", g.to_string()));
                    g
                }
                None => {
                    cx.span().add_event(
                        "Failed to Retreive Guild ID",
                        vec![KeyValue::new("Server ID", "None")],
                    );
                    cx.span()
                        .set_status(opentelemetry::trace::Status::error("Failed"));
                    return;
                }
            };

            let channel_id = match get_user_voice_channel(&ctx, &msg) {
                Some(c) => {
                    cx.span().add_event(
                        "Received Channel ID",
                        vec![KeyValue::new("Channel ID", c.to_string())],
                    );
                    cx.span().set_status(Status::Ok);
                    c
                }
                None => {
                    cx.span().add_event(
                        "Failed to Retreive Channel ID",
                        vec![KeyValue::new("Channel ID", "None")],
                    );
                    cx.span()
                        .set_status(opentelemetry::trace::Status::error(format!(
                            "{} not in Voice Channel",
                            msg.author
                        )));
                    let _ = msg
                        .channel_id
                        .say(&ctx.http, "You must be in a voice channel")
                        .await;
                    return;
                }
            };
            let manager = get(&ctx).await.expect("Songbird not initialized").clone();

            let _ = manager.join(guild_id, channel_id).await;

            let _ = msg
                .channel_id
                .say(&ctx.http, "Joined your voice channel!")
                .await;
            cx.span().add_event(
                "Joined Voice Channel",
                vec![KeyValue::new("Voice Channel", msg.channel_id.to_string())],
            );
            cx.span().set_status(Status::Ok);
        }
        if msg.content == "!leave" {
            let guild_id = match msg.guild_id {
                Some(g) => g,
                None => return,
            };
            let manager = songbird::get(&ctx).await.unwrap();
            if manager.get(guild_id).is_some() {
                let _ = manager.remove(guild_id).await;
                let _ = msg
                    .channel_id
                    .say(&ctx.http, "Left the voice channel")
                    .await;
            }
        }
        if msg.content.starts_with("!play") {
            let tracer = global::tracer("RhythmWorks");
            let span = tracer.start("Play Message Received");
            let mut yt_url = msg
                .content
                .strip_prefix("!play ")
                .unwrap_or("")
                .trim()
                .to_string();
            if let Some(offset) = yt_url.find('&') {
                yt_url.truncate(offset);
            }
            if yt_url.is_empty() {
                let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await;
                return;
            }
            let cx = opentelemetry::Context::current_with_span(span);
            cx.span()
                .set_attribute(KeyValue::new("Youtube_URL", yt_url.to_string()));
            let guild_id = match msg.guild_id {
                Some(g) => {
                    cx.span().add_event(
                        "Received Guild ID",
                        vec![KeyValue::new("Server ID", g.to_string())],
                    );
                    cx.span().set_status(opentelemetry::trace::Status::Ok);
                    cx.span()
                        .set_attribute(KeyValue::new("Guild_ID", g.to_string()));
                    g
                }
                None => {
                    cx.span().add_event(
                        "Failed to Retreive Guild ID",
                        vec![KeyValue::new("Server ID", "None")],
                    );
                    cx.span()
                        .set_status(opentelemetry::trace::Status::error("Failed"));
                    return;
                }
            };
            let manager = songbird::get(&ctx).await.unwrap().clone();
            let handler_lock = if let Some(call) = manager.get(guild_id) {
                call
            } else {
                let channel_id = match get_user_voice_channel(&ctx, &msg) {
                    Some(c) => c,
                    None => {
                        cx.span().add_event(
                            "Failed to Retreive Channel ID",
                            vec![KeyValue::new("Channel ID", "None")],
                        );
                        cx.span()
                            .set_status(opentelemetry::trace::Status::error(format!(
                                "{} not in Voice Channel",
                                msg.author
                            )));
                        let _ = msg
                            .channel_id
                            .say(&ctx.http, "You must be in a voice channel")
                            .await;
                        return;
                    }
                };
                manager.join(guild_id, channel_id).await.unwrap()
            };
            let output = match Command::new("./yt-dlp")
                .args(["-f", "bestaudio", "-g", "--no-playlist", &yt_url])
                .stdout(Stdio::piped())
                .output()
            {
                Ok(output) => {
                    cx.span().add_event(
                        "Retreiving Youtube Information",
                        vec![KeyValue::new("Success", yt_url.to_string())],
                    );
                    output
                }
                Err(e) => {
                    cx.span().add_event(
                        "Retreiving Youtube Information",
                        vec![KeyValue::new("Failed", e.to_string())],
                    );
                    cx.span().set_status(Status::error("Failed"));
                    eprintln!("{}", e);
                    return;
                }
            };
            let stream_url = String::from_utf8(output.stdout)
                .expect("Invalid UTF-8")
                .trim()
                .to_string();
            let telemetry_stream_url = stream_url.clone();
            let mut call = handler_lock.lock().await;
            let input = Input::from(HlsRequest::new(reqwest::Client::new(), stream_url));
            let track_handle = call.enqueue_input(input).await;
            let otel_ctx = opentelemetry::Context::current();
            let position = call.queue().len();
            let response = if position == 1 {
                cx.span().add_event(
                    "Starting Playback",
                    vec![KeyValue::new("Status", "Playing".to_string())],
                );
                "🎶 Now playing!".to_string()
            } else {
                cx.span().add_event(
                    "Starting Playback",
                    vec![KeyValue::new("Status", "Added to Queue".to_string())],
                );
                format!("🎶 Added Song to Queue: #{}", position)
            };
            let _ = msg.channel_id.say(&ctx.http, response).await;
            let _ = track_handle.add_event(
                Event::Track(songbird::events::TrackEvent::Play),
                TrackTraceHandler {
                    otel_ctx: otel_ctx.clone(),
                    guild_id: guild_id.to_string().clone(),
                    track_url: yt_url.clone(),
                    hls_url: telemetry_stream_url.clone(),
                    event_name: "Track Started Playing",
                },
            );
            let _ = track_handle.add_event(
                Event::Track(songbird::events::TrackEvent::End),
                TrackTraceHandler {
                    otel_ctx: otel_ctx.clone(),
                    guild_id: guild_id.to_string().clone(),
                    track_url: yt_url.clone(),
                    hls_url: telemetry_stream_url.clone(),
                    event_name: "Track Finished",
                },
            );
            let _ = track_handle.add_event(
                Event::Track(songbird::events::TrackEvent::Error),
                TrackTraceHandler {
                    otel_ctx: otel_ctx.clone(),
                    guild_id: guild_id.to_string().clone(),
                    track_url: yt_url.clone(),
                    hls_url: telemetry_stream_url.clone(),
                    event_name: "Playback had an error",
                },
            );
        }
        if msg.content == "!skip" {
            let guild_id = match msg.guild_id {
                Some(g) => g,
                None => return,
            };
            let manager = songbird::get(&ctx).await.unwrap();
            if let Some(call) = manager.get(guild_id) {
                let call = call.lock().await;
                let _ = call.queue().skip();
                let _ = msg.channel_id.say(&ctx.http, "Skipped").await;
            }
        }
        if msg.content == "!pause" {
            let guild_id = match msg.guild_id {
                Some(g) => g,
                None => return,
            };
            let manager = songbird::get(&ctx).await.unwrap();
            if let Some(call) = manager.get(guild_id) {
                let call = call.lock().await;
                let _ = call.queue().pause();
            }
        }
        if msg.content == "!resume" {
            let guild_id = match msg.guild_id {
                Some(g) => g,
                None => return,
            };
            let manager = songbird::get(&ctx).await.unwrap();
            if let Some(call) = manager.get(guild_id) {
                let call = call.lock().await;
                let _ = call.queue().resume();
            }
        }
        if msg.content == "!stop" {
            let guild_id = match msg.guild_id {
                Some(g) => g,
                None => return,
            };
            let manager = songbird::get(&ctx).await.unwrap();
            if let Some(call) = manager.get(guild_id) {
                let call = call.lock().await;
                let _ = call.queue().stop();
            }
        }
    }
    async fn ready(&self, _: Context, ready: Ready) {
        println!("{} is connected!", ready.user.name);
    }
}
async fn run_bot(token: String, shutdown: CancellationToken) {
    let intents = GatewayIntents::GUILD_MESSAGES
        | GatewayIntents::GUILDS
        | GatewayIntents::DIRECT_MESSAGES
        | GatewayIntents::MESSAGE_CONTENT
        | GatewayIntents::GUILD_VOICE_STATES;

    let mut client = Client::builder(&token, intents)
        .event_handler(Handler)
        .register_songbird()
        .await
        .expect("Err creating client");

    tokio::select! {
        result = client.start() => {
            if let Err(why) = result {
                println!("Client error: {why:?}");
            }
        }
        _ = shutdown.cancelled() => {
            println!("Shutting down bot...");
        }
    }
}

#[tokio::main]
async fn main() {
    println!(
        "RhythmWorks  Copyright (C) 2025  James Brotosky\n
This program comes with ABSOLUTELY NO WARRANTY\n
This is free software, and you are welcome to redistribute it under certain conditions."
    );
    let (tracer_provider, logger_provider) = telemetry::init_telemetry();
    global::set_tracer_provider(tracer_provider.clone());
    let filter = Targets::new()
        .with_target("RhythmWorks", LevelFilter::INFO)
        .with_default(LevelFilter::OFF);
    let otel_log_layer =
        opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge::new(&logger_provider)
            .with_filter(filter);
    tracing_subscriber::registry().with(otel_log_layer).init();
    let tracer: global::BoxedTracer = global::tracer("tracer");
    tracer
        .in_span("Checking for Updates", |_cx| updator::update())
        .await;
    let service_name = "RhythmWorks";
    let username = env::var("USER").or_else(|_| env::var("USERNAME")).unwrap();
    let mut bot_task: Option<JoinHandle<()>> = None;
    let mut shutdown_token: Option<CancellationToken> = None;
    let mut bot_state = BotState::Stopped;
    loop {
        let token = credentialmanager::get_token(service_name, &username);
        let options: Vec<&'static str> = vec![
            "- Start Bot",
            "- Stop Bot",
            "- Change Token",
            "- Delete Token",
            "- Exit",
        ];
        let selection = Select::with_theme(&ColorfulTheme::default())
            .with_prompt("Choose an Option")
            .default(0)
            .items(&options)
            .interact()
            .unwrap();
        match selection {
            0 => {
                if matches!(bot_state, BotState::Running) {
                    println!("[x] Bot is already running.");
                    continue;
                }

                let token = credentialmanager::get_token(service_name, &username);
                let shutdown = CancellationToken::new();
                let shutdown_clone = shutdown.clone();

                let task = tokio::spawn(run_bot(token, shutdown_clone));

                bot_task = Some(task);
                shutdown_token = Some(shutdown);
                bot_state = BotState::Running;

                println!("✅ Bot started.");
            }
            1 => {
                if let Some(token) = shutdown_token.take() {
                    token.cancel();
                }
                if let Some(task) = bot_task.take() {
                    let _ = task.await;
                }
                println!("Bot stopped.");
            }
            2 => {
                credentialmanager::change_token(service_name, &username);
            }
            3 => {
                credentialmanager::remove_token(service_name, &username);
            }
            4 => {
                if let Some(token) = shutdown_token {
                    token.cancel();
                }
                break;
            }
            _ => println!("Invalid Option"),
        }
    }
}
