use std::sync::OnceLock;
use tauri::{AppHandle, Emitter, Manager, State};
use tauri_plugin_shell::process::CommandEvent;
use tauri_plugin_shell::ShellExt;
use tokio_util::sync::CancellationToken;

static AGENTKIT_PORT_RE: OnceLock<regex::Regex> = OnceLock::new();
static UVICORN_PORT_RE: OnceLock<regex::Regex> = OnceLock::new();

#[tauri::command]
pub async fn start_backend(
    app: AppHandle,
    state: State<'_, crate::BackendState>,
) -> Result<u16, String> {
    // If already started, return existing port
    if let Some(port) = *state.port.lock().unwrap() {
        return Ok(port);
    }

    // Spawn sidecar with --port 0 for random port assignment
    let sidecar_command = app
        .shell()
        .sidecar("agentkit-server")
        .map_err(|e| format!("Failed to create sidecar command: {}", e))?
        .args(["--port", "0", "--host", "127.0.0.1"])
        .env("AGENTKIT_GUI_MODE", "1");

    let (mut rx, child) = sidecar_command
        .spawn()
        .map_err(|e| format!("Failed to spawn sidecar: {}", e))?;

    // Store the CommandChild for proper cleanup later
    *state.child.lock().unwrap() = Some(child);

    let port_clone = state.port.clone();
    let cancel_token = CancellationToken::new();
    *state.cancel_token.lock().unwrap() = Some(cancel_token.clone());
    let app_clone = app.clone();

    // Listen for stdout/stderr to detect port
    let cancel = cancel_token.clone();
    tokio::spawn(async move {
        tokio::select! {
            _ = cancel.cancelled() => {
                log::info!("Backend listener task cancelled");
            }
            result = async {
                while let Some(event) = rx.recv().await {
                    match event {
                        CommandEvent::Stdout(line) => {
                            let output = String::from_utf8_lossy(&line);
                            log::info!("[backend:stdout] {}", output.trim());
                            if let Some(port) = extract_port(&output) {
                                *port_clone.lock().unwrap() = Some(port);
                                let _ = app_clone.emit("backend-ready", serde_json::json!({ "port": port }));
                                break;
                            }
                        }
                        CommandEvent::Stderr(line) => {
                            let output = String::from_utf8_lossy(&line);
                            log::info!("[backend:stderr] {}", output.trim());
                            if let Some(port) = extract_port(&output) {
                                *port_clone.lock().unwrap() = Some(port);
                                let _ = app_clone.emit("backend-ready", serde_json::json!({ "port": port }));
                                break;
                            }
                        }
                        CommandEvent::Terminated(payload) => {
                            log::error!("Backend process terminated: {:?}", payload);
                            break;
                        }
                        _ => {}
                    }
                }
            } => { result }
        }
    });

    // Wait for port to be ready (with timeout)
    let max_wait = std::time::Duration::from_secs(30);
    let start = std::time::Instant::now();
    while start.elapsed() < max_wait {
        {
            let guard = state.port.lock().unwrap();
            if let Some(port) = *guard {
                log::info!("Backend started on port {}", port);

                // Close splash, show main window
                if let Some(splash) = app.get_webview_window("splash") {
                    let _ = splash.close();
                }
                if let Some(main) = app.get_webview_window("main") {
                    let _ = main.show();
                    let _ = main.set_focus();
                }

                return Ok(port);
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
    }

    // Timeout: clean up the started backend process
    log::warn!("Backend startup timed out, cleaning up...");
    cleanup_backend(&state);
    Err("Backend startup timed out after 30 seconds".into())
}

fn extract_port(output: &str) -> Option<u16> {
    let re = AGENTKIT_PORT_RE.get_or_init(|| regex::Regex::new(r"AGENTKIT_PORT=(\d{1,5})").unwrap());
    if let Some(caps) = re.captures(output) {
        return caps[1].parse().ok();
    }
    let re2 = UVICORN_PORT_RE.get_or_init(|| regex::Regex::new(r"Uvicorn running on https?://[\d.]+:(\d{1,5})").unwrap());
    if let Some(caps) = re2.captures(output) {
        return caps[1].parse().ok();
    }
    None
}

/// Clean up backend process: cancel listener task, kill child process, reset state.
fn cleanup_backend(state: &State<'_, crate::BackendState>) {
    // Cancel the listener task
    if let Some(token) = state.cancel_token.lock().unwrap().take() {
        token.cancel();
    }
    // Kill the child process
    if let Some(child) = state.child.lock().unwrap().take() {
        if let Err(e) = child.kill() {
            log::warn!("Failed to kill backend child process: {}", e);
        }
    }
    // Reset state
    *state.port.lock().unwrap() = None;
}

#[tauri::command]
pub async fn get_backend_port(state: State<'_, crate::BackendState>) -> Result<u16, String> {
    state
        .port
        .lock()
        .unwrap()
        .ok_or_else(|| "Backend not started".into())
}

#[tauri::command]
pub async fn stop_backend(state: State<'_, crate::BackendState>) -> Result<(), String> {
    cleanup_backend(&state);
    Ok(())
}

#[tauri::command]
pub async fn check_backend_health(
    state: State<'_, crate::BackendState>,
) -> Result<bool, String> {
    let port = state.port.lock().unwrap().ok_or("Backend not started")?;
    let url = format!("http://127.0.0.1:{}/api/v1/health", port);
    let client = reqwest::Client::new();
    let resp = client
        .get(&url)
        .timeout(std::time::Duration::from_secs(3))
        .send()
        .await;
    Ok(resp.map(|r| r.status().is_success()).unwrap_or(false))
}
