use std::sync::Arc;
use tokio::process::{Child, Command};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;

type ProcessHandle = Arc<Mutex<Option<Child>>>;

pub async fn run(release: bool) {
    let qjackctl_handle: ProcessHandle = Arc::new(Mutex::new(None));
    let audio_engine_handle: ProcessHandle = Arc::new(Mutex::new(None));
    let gui_handle: ProcessHandle = Arc::new(Mutex::new(None));
    let simulator_handle: ProcessHandle = Arc::new(Mutex::new(None));

    let cancel_token = CancellationToken::new();

    // Set up signal handling for graceful shutdown
    let cleanup_handles = vec![
        qjackctl_handle.clone(),
        audio_engine_handle.clone(),
        gui_handle.clone(),
        simulator_handle.clone(),
    ];
    let cleanup_token = cancel_token.clone();

    tokio::spawn(async move {
        tokio::signal::ctrl_c()
            .await
            .expect("Failed to listen for ctrl+c");
        println!("Received Ctrl+C, shutting down...");
        cleanup_token.cancel();
        cleanup_processes(cleanup_handles).await;
        stop_jack_daemon().await;
    });

    // Start processes
    let qjackctl = spawn_qjackctl().await;
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    let audio_engine = spawn_audio_engine(release).await;
    let gui = spawn_gui(release).await;
    let simulator = spawn_simulator(release).await;

    // Store handles for cleanup
    *qjackctl_handle.lock().await = Some(qjackctl);
    *audio_engine_handle.lock().await = Some(audio_engine);
    *gui_handle.lock().await = Some(gui);
    *simulator_handle.lock().await = Some(simulator);

    // Get references back for waiting
    let mut qjackctl = qjackctl_handle.lock().await.take().unwrap();
    let mut audio_engine = audio_engine_handle.lock().await.take().unwrap();
    let mut gui = gui_handle.lock().await.take().unwrap();
    let mut simulator = simulator_handle.lock().await.take().unwrap();

    // Wait for any process to exit or cancellation
    tokio::select! {
        result = qjackctl.wait() => {
            println!("qjackctl exited: {:?}", result);
            kill_process(&mut audio_engine, "audio_engine").await;
            kill_process(&mut gui, "gui").await;
            kill_process(&mut simulator, "simulator").await;
            stop_jack_daemon().await;
        }
        result = audio_engine.wait() => {
            println!("audio_engine exited: {:?}", result);
            kill_process(&mut qjackctl, "qjackctl").await;
            kill_process(&mut gui, "gui").await;
            kill_process(&mut simulator, "simulator").await;
            stop_jack_daemon().await;
        }
        result = gui.wait() => {
            println!("gui exited: {:?}", result);
            kill_process(&mut qjackctl, "qjackctl").await;
            kill_process(&mut audio_engine, "audio_engine").await;
            kill_process(&mut simulator, "simulator").await;
            stop_jack_daemon().await;
        }
        result = simulator.wait() => {
            println!("simulator exited: {:?}", result);
            kill_process(&mut qjackctl, "qjackctl").await;
            kill_process(&mut audio_engine, "audio_engine").await;
            kill_process(&mut gui, "gui").await;
            stop_jack_daemon().await;
        }
        _ = cancel_token.cancelled() => {
            println!("Shutdown requested");
            kill_process(&mut qjackctl, "qjackctl").await;
            kill_process(&mut audio_engine, "audio_engine").await;
            kill_process(&mut gui, "gui").await;
            kill_process(&mut simulator, "simulator").await;
            stop_jack_daemon().await;
        }
    }

    println!("All processes stopped");
}

async fn cleanup_processes(handles: Vec<ProcessHandle>) {
    for handle in handles {
        if let Some(mut child) = handle.lock().await.take() {
            let _ = child.kill().await;
        }
    }
}

async fn stop_jack_daemon() {
    println!("Stopping JACK daemon...");

    #[cfg(target_os = "windows")]
    let (jack, args) = ("taskkill", vec!["/f", "/im", "jackd.exe"]);

    #[cfg(target_os = "linux")]
    let (jack, args) = ("pkill", vec!["-f", "jackd"]);

    Command::new(jack)
        .args(args)
        .output()
        .await
        .expect("Failed to stop JACK daemon");
}

async fn spawn_qjackctl() -> Child {
    #[cfg(target_os = "windows")]
    let qjackctl = "C:\\Program Files\\JACK2\\qjackctl\\qjackctl.exe";
    #[cfg(target_os = "linux")]
    let qjackctl = "qjackctl";

    Command::new(qjackctl)
        .arg("/start-server")
        .spawn()
        .expect("Could not start qjackctl")
}

async fn spawn_audio_engine(release: bool) -> Child {
    Command::new("cargo")
        .args([
            "run",
            "--profile",
            if release { "release" } else { "debug" },
            "--bin",
            "audio_engine",
            "--",
            "--config-path",
            "config",
        ])
        .spawn()
        .expect("Could not start audio engine")
}

async fn spawn_gui(release: bool) -> Child {
    Command::new("cargo")
        .args([
            "run",
            "--profile",
            if release { "release" } else { "debug" },
            "--bin",
            "gui"
        ])
        .spawn()
        .expect("Could not start gui")
}

async fn spawn_simulator(release: bool) -> Child {
    if release {
        Command::new("sleep")
            .args(["infinity"])
            .spawn()
            .expect("Could not start simulator")
    } else {
        Command::new("cargo")
            .args(["run", "--release", "--bin", "simulator"])
            .spawn()
            .expect("Could not start simulator")
    }
}

async fn kill_process(child: &mut Child, name: &str) {
    match child.kill().await {
        Ok(()) => println!("Killed {}", name),
        Err(e) => eprintln!("Failed to kill {}: {}", name, e),
    }
}
