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 mapper() {
    let qjackctl_handle: ProcessHandle = Arc::new(Mutex::new(None));
    let firmware_handle: ProcessHandle = Arc::new(Mutex::new(None));
    let mapper_handle: ProcessHandle = Arc::new(Mutex::new(None));

    let cancel_token = CancellationToken::new();

    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();
    });

    // Start processes
    println!("Starting qjackctl...");
    let qjackctl = spawn_qjackctl().await;
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("Starting firmware debugger in new terminal...");
    let firmware = spawn_firmware_in_terminal().await;

    println!("Starting mapper...");
    let mapper = spawn_mapper().await;

    // Store handles for cleanup
    *qjackctl_handle.lock().await = Some(qjackctl);
    *firmware_handle.lock().await = Some(firmware);
    *mapper_handle.lock().await = Some(mapper);

    // Get references back for waiting (firmware not monitored since gnome-terminal exits immediately)
    let mut qjackctl = qjackctl_handle.lock().await.take().unwrap();
    let mut mapper = mapper_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 mapper, "mapper").await;
            cleanup_processes(vec![firmware_handle]).await;
            stop_jack_daemon().await;
        }
        result = mapper.wait() => {
            println!("mapper exited: {:?}", result);
            kill_process(&mut qjackctl, "qjackctl").await;
            cleanup_processes(vec![firmware_handle]).await;
            stop_jack_daemon().await;
        }
        _ = cancel_token.cancelled() => {
            println!("Shutdown requested");
            kill_process(&mut mapper, "mapper").await;
            kill_process(&mut qjackctl, "qjackctl").await;
            cleanup_processes(vec![firmware_handle]).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_firmware_in_terminal() -> Child {
    #[cfg(target_os = "windows")]
    {
        Command::new("C:\\Program Files\\Git\\git-bash.exe")
            .args([
                "-c",
                "cd firmware && cargo run; read -p 'Press Enter to close...'",
            ])
            .spawn()
            .expect("Could not start firmware debugger in git-bash")
    }

    #[cfg(target_os = "linux")]
    {
        Command::new("gnome-terminal")
            .args([
                "--title=Firmware Debugger - FCB Looper",
                "--",
                "bash", "-c", 
                "cd firmware && echo 'Starting firmware debugger...' && cargo run; echo 'Firmware debugger exited. Press Enter to close...'; read"
            ])
            .spawn()
            .expect("Could not start firmware debugger in gnome-terminal")
    }
}

async fn spawn_mapper() -> Child {
    Command::new("cargo")
        .args(["run", "--package", "mapper"])
        .stdin(std::process::Stdio::inherit())
        .stdout(std::process::Stdio::inherit())
        .stderr(std::process::Stdio::inherit())
        .spawn()
        .expect("Could not start mapper")
}

async fn kill_process(child: &mut Child, name: &str) {
    match child.kill().await {
        Ok(()) => println!("Stopped {}", name),
        Err(e) => {
            // Don't print error if process already exited
            let error_msg = e.to_string().to_lowercase();
            if !error_msg.contains("no such process")
                && !error_msg.contains("not found")
                && !error_msg.contains("invalid argument")
            {
                eprintln!("Failed to stop {}: {}", name, e);
            }
        }
    }
}
