use std::sync::Mutex;
use tauri::{
    menu::{Menu, MenuItem},
    tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
    Manager, Runtime,
};
use tauri_plugin_deep_link::DeepLinkExt;

/// Holds the child process we spawned, if any.
/// None means the proxy was already running when the app started (we don't own it).
struct ProxyProcess(Mutex<Option<std::process::Child>>);

/// Find the `bibividi` executable.
/// GUI apps on macOS/Linux don't inherit the shell PATH, so we check common locations.
fn find_bibividi() -> Option<std::path::PathBuf> {
    // Common pip install locations in addition to PATH
    let candidates: Vec<std::path::PathBuf> = {
        let mut v = vec![];

        // Expand PATH entries
        if let Ok(path_var) = std::env::var("PATH") {
            for dir in std::env::split_paths(&path_var) {
                v.push(dir.join(if cfg!(windows) { "bibividi.exe" } else { "bibividi" }));
            }
        }

        // Common locations not always in GUI PATH
        #[cfg(unix)]
        {
            if let Ok(home) = std::env::var("HOME") {
                v.push(std::path::PathBuf::from(&home).join(".local/bin/bibividi"));
                v.push(std::path::PathBuf::from(&home).join(".pyenv/shims/bibividi"));
            }
            v.push(std::path::PathBuf::from("/usr/local/bin/bibividi"));
            v.push(std::path::PathBuf::from("/opt/homebrew/bin/bibividi"));
        }

        #[cfg(windows)]
        if let Ok(appdata) = std::env::var("APPDATA") {
            // e.g. C:\Users\<name>\AppData\Roaming\Python\Python3x\Scripts
            for entry in std::fs::read_dir(std::path::PathBuf::from(&appdata).join("Python"))
                .into_iter()
                .flatten()
                .flatten()
            {
                v.push(entry.path().join("Scripts").join("bibividi.exe"));
            }
        }

        v
    };

    candidates.into_iter().find(|p| p.exists())
}

/// Spawn `bibividi start` if the proxy isn't already listening on :8080.
/// Returns the child process if we spawned it, None if it was already running or spawn failed.
fn try_spawn_proxy() -> Option<std::process::Child> {
    // Already running externally — don't touch it
    if std::net::TcpStream::connect("127.0.0.1:8080").is_ok() {
        return None;
    }

    let exe = find_bibividi()?;

    let mut cmd = std::process::Command::new(&exe);
    cmd.arg("start");

    // On Windows: don't flash a console window
    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
    }

    cmd.spawn().ok()
}

/// Kill the proxy process we own, if any.
fn kill_proxy(app: &tauri::AppHandle) {
    if let Ok(mut guard) = app.state::<ProxyProcess>().0.lock() {
        if let Some(mut child) = guard.take() {
            let _ = child.kill();
        }
    }
}

/// Extract the license key from a bibividi://activate?key=bvd-xxx URL.
fn extract_key_from_url(url: &str) -> Option<String> {
    // Minimal parser — avoids pulling in the url crate.
    // Expected format: bibividi://activate?key=bvd-xxxxxxxx
    let query = url.split('?').nth(1)?;
    for pair in query.split('&') {
        let mut parts = pair.splitn(2, '=');
        if parts.next() == Some("key") {
            return parts.next().map(|v| v.to_owned());
        }
    }
    None
}

/// Kill and re-spawn the proxy child process.
/// Called by the wizard after writing ~/.bibividi/.env so the new API key is loaded.
#[tauri::command]
async fn restart_proxy(app: tauri::AppHandle) -> Result<(), String> {
    kill_proxy(&app);
    // Small delay so the old process fully releases :8080
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    let child = try_spawn_proxy();
    if let Ok(mut guard) = app.state::<ProxyProcess>().0.lock() {
        *guard = child;
    }
    // Poll until :8080 is accepting connections (max 10 s)
    for _ in 0..100 {
        if std::net::TcpStream::connect("127.0.0.1:8080").is_ok() {
            return Ok(());
        }
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    }
    Err("Proxy did not come back online within 10 s".to_owned())
}

/// Open the dashboard in the main window (called from tray).
#[tauri::command]
fn show_window<R: Runtime>(app: tauri::AppHandle<R>) {
    if let Some(win) = app.get_webview_window("main") {
        let _ = win.show();
        let _ = win.set_focus();
    }
}

/// Proxy status check — calls the BIBIVIDI health endpoint.
/// Returns true if the proxy is reachable.
#[tauri::command]
async fn proxy_status() -> bool {
    // Simple TCP connect check — avoids adding reqwest as a dep
    std::net::TcpStream::connect("127.0.0.1:8080").is_ok()
}

/// Check for updates and return Some(version) if one is available.
/// Called by the frontend on startup; the UI decides whether to prompt.
#[tauri::command]
async fn check_for_update<R: Runtime>(
    app: tauri::AppHandle<R>,
) -> Result<Option<String>, String> {
    use tauri_plugin_updater::UpdaterExt;
    match app.updater() {
        Ok(updater) => match updater.check().await {
            Ok(Some(update)) => Ok(Some(update.version.clone())),
            Ok(None) => Ok(None),
            Err(e) => Err(e.to_string()),
        },
        Err(e) => Err(e.to_string()),
    }
}

/// Download and install a pending update, then restart.
#[tauri::command]
async fn install_update<R: Runtime>(
    app: tauri::AppHandle<R>,
) -> Result<(), String> {
    use tauri_plugin_updater::UpdaterExt;
    let updater = app.updater().map_err(|e| e.to_string())?;
    let update = updater
        .check()
        .await
        .map_err(|e| e.to_string())?
        .ok_or_else(|| "No update available".to_string())?;

    update
        .download_and_install(|_, _| {}, || {})
        .await
        .map_err(|e| e.to_string())?;

    app.restart();
}

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_notification::init())
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(tauri_plugin_deep_link::init())
        .invoke_handler(tauri::generate_handler![
            show_window,
            proxy_status,
            check_for_update,
            install_update,
            restart_proxy,
        ])
        .setup(|app| {
            // ── Start proxy if not already running ────────────────────────
            let child = try_spawn_proxy();
            app.manage(ProxyProcess(Mutex::new(child)));

            // ── Deep link handler — bibividi://activate?key=bvd-xxx ───────
            let handle = app.handle().clone();
            app.deep_link().on_open_url(move |event| {
                for url in event.urls() {
                    if let Some(key) = extract_key_from_url(url.as_str()) {
                        handle.emit("activate-key", key).ok();
                        // Bring window to front so the wizard is visible
                        if let Some(win) = handle.get_webview_window("main") {
                            let _ = win.show();
                            let _ = win.set_focus();
                        }
                    }
                }
            });

            // ── System tray ───────────────────────────────────────────────
            let open_i = MenuItem::with_id(app, "open", "Open Dashboard", true, None::<&str>)?;
            let quit_i = MenuItem::with_id(app, "quit", "Quit BIBIVIDI",  true, None::<&str>)?;
            let menu = Menu::with_items(app, &[&open_i, &quit_i])?;

            TrayIconBuilder::new()
                .menu(&menu)
                .show_menu_on_left_click(false)
                .on_menu_event(|app, event| match event.id.as_ref() {
                    "open" => show_window(app.clone()),
                    "quit" => {
                        kill_proxy(app);
                        app.exit(0);
                    }
                    _ => {}
                })
                .on_tray_icon_event(|tray, event| {
                    if let TrayIconEvent::Click {
                        button: MouseButton::Left,
                        button_state: MouseButtonState::Up,
                        ..
                    } = event
                    {
                        show_window(tray.app_handle().clone());
                    }
                })
                .build(app)?;

            Ok(())
        })
        // Kill proxy when the OS closes the app (e.g. system shutdown, force quit)
        .on_window_event(|window, event| {
            if let tauri::WindowEvent::Destroyed = event {
                kill_proxy(window.app_handle());
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running BIBIVIDI desktop app");
}
