use std::collections::HashMap;
use std::sync::Mutex;
use std::thread;

use mdns_sd::{ServiceDaemon, ServiceEvent};
use serde_json::json;
use tauri::{AppHandle, Manager, State};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_shell::ShellExt;

struct SidecarState {
    child: Mutex<Option<CommandChild>>,
}

struct AppState {
    known_devices: Mutex<HashMap<String, String>>,
}

#[tauri::command]
async fn list_cast_devices(state: State<'_, AppState>) -> Result<Vec<String>, String> {
    let devices = state.known_devices.lock().unwrap();
    let mut list: Vec<String> = devices.keys().cloned().collect();
    list.sort();
    Ok(list)
}

#[tauri::command]
async fn cast_play(
    app: AppHandle,
    state: State<'_, AppState>,
    sidecar_state: State<'_, SidecarState>,
    device_name: String,
    url: String,
) -> Result<(), String> {
    let ip = {
        let devices = state.known_devices.lock().unwrap();
        devices
            .get(&device_name)
            .cloned()
            .ok_or("Device not found")?
    };

    let mut lock = sidecar_state.child.lock().unwrap();

    // Get or spawn child
    let child = if let Some(ref mut child) = *lock {
        child
    } else {
        println!("Spawning new sidecar...");
        let sidecar_command = app
            .shell()
            .sidecar("radiocast-sidecar")
            .map_err(|e| e.to_string())?;
        let (mut rx, child) = sidecar_command.spawn().map_err(|e| e.to_string())?;

        tauri::async_runtime::spawn(async move {
            while let Some(event) = rx.recv().await {
                match event {
                    CommandEvent::Stdout(line) => {
                        println!("Sidecar: {}", String::from_utf8_lossy(&line))
                    }
                    CommandEvent::Stderr(line) => {
                        eprintln!("Sidecar Error: {}", String::from_utf8_lossy(&line))
                    }
                    _ => {}
                }
            }
        });

        *lock = Some(child);
        lock.as_mut().unwrap()
    };

    let play_cmd = json!({
        "command": "play",
        "args": { "ip": ip, "url": url }
    });

    child
        .write(format!("{}\n", play_cmd.to_string()).as_bytes())
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
async fn cast_stop(
    _app: AppHandle,
    sidecar_state: State<'_, SidecarState>,
    _device_name: String,
) -> Result<(), String> {
    let mut lock = sidecar_state.child.lock().unwrap();
    if let Some(ref mut child) = *lock {
        let stop_cmd = json!({ "command": "stop", "args": {} });
        child
            .write(format!("{}\n", stop_cmd.to_string()).as_bytes())
            .map_err(|e| e.to_string())?;
    }
    Ok(())
}

#[tauri::command]
async fn cast_set_volume(
    _app: AppHandle,
    sidecar_state: State<'_, SidecarState>,
    _device_name: String,
    volume: f32,
) -> Result<(), String> {
    let mut lock = sidecar_state.child.lock().unwrap();
    if let Some(ref mut child) = *lock {
        let vol_cmd = json!({ "command": "volume", "args": { "level": volume } });
        child
            .write(format!("{}\n", vol_cmd.to_string()).as_bytes())
            .map_err(|e| e.to_string())?;
    }
    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_opener::init())
        .setup(|app| {
            app.manage(AppState {
                known_devices: Mutex::new(HashMap::new()),
            });
            app.manage(SidecarState {
                child: Mutex::new(None),
            });

            let handle = app.handle().clone();
            thread::spawn(move || {
                let mdns = ServiceDaemon::new().expect("Failed to create daemon");
                let receiver = mdns
                    .browse("_googlecast._tcp.local.")
                    .expect("Failed to browse");
                while let Ok(event) = receiver.recv() {
                    match event {
                        ServiceEvent::ServiceResolved(info) => {
                            let name = info
                                .get_property_val_str("fn")
                                .or_else(|| Some(info.get_fullname()))
                                .unwrap()
                                .to_string();
                            let addresses = info.get_addresses();
                            let ip = addresses
                                .iter()
                                .find(|ip| ip.is_ipv4())
                                .or_else(|| addresses.iter().next());

                            if let Some(ip) = ip {
                                let state = handle.state::<AppState>();
                                let mut devices = state.known_devices.lock().unwrap();
                                let ip_str = ip.to_string();
                                if !devices.contains_key(&name) {
                                    println!("Discovered Cast Device: {} at {}", name, ip_str);
                                    devices.insert(name, ip_str);
                                }
                            }
                        }
                        _ => {}
                    }
                }
            });
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            list_cast_devices,
            cast_play,
            cast_stop,
            cast_set_volume
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
