//! Gateway rebuild — builds the new huskies binary and hands off to the trampoline.
//!
//! The trampoline is spawned as a detached process (new Unix session) so that it
//! survives the gateway's own death.  On success the gateway continues running
//! until the trampoline kills it; the new gateway then posts "gateway X.Y.Z ready".

use std::path::Path;

/// Build the huskies binary and launch the detached trampoline to swap the gateway.
///
/// Returns `Err(message)` (shown to the user in chat) if the build or trampoline
/// launch fails.  On success returns `Ok(())` — the trampoline is now running
/// in a detached process and will kill this gateway and replace it with the new
/// binary within 10 s.
pub async fn rebuild_gateway(config_dir: &Path, gateway_port: u16) -> Result<(), String> {
    let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
    let workspace_root = manifest_dir
        .parent()
        .ok_or("cannot determine workspace root from CARGO_MANIFEST_DIR")?;

    crate::slog!(
        "[gateway-rebuild] Building from workspace root: {}",
        workspace_root.display()
    );

    // Rebuild the frontend bundle so rust-embed picks up the latest assets.
    let frontend_dir = workspace_root.join("frontend");
    if frontend_dir.join("package.json").exists() {
        crate::slog!("[gateway-rebuild] Building frontend");
        let fe_output = tokio::task::spawn_blocking({
            let dir = frontend_dir.clone();
            move || {
                std::process::Command::new("npm")
                    .args(["run", "build"])
                    .current_dir(&dir)
                    .output()
            }
        })
        .await
        .map_err(|e| format!("frontend build task panicked: {e}"))?
        .map_err(|e| format!("failed to run npm run build: {e}"))?;

        if !fe_output.status.success() {
            let stderr = String::from_utf8_lossy(&fe_output.stderr);
            return Err(format!("Frontend build failed:\n{stderr}"));
        }
        crate::slog!("[gateway-rebuild] Frontend build succeeded");
    }

    // Build the server binary matching the current profile.
    let build_args: Vec<&str> = if cfg!(debug_assertions) {
        vec!["build", "-p", "huskies"]
    } else {
        vec!["build", "--release", "-p", "huskies"]
    };
    crate::slog!("[gateway-rebuild] cargo {}", build_args.join(" "));

    let output = tokio::task::spawn_blocking({
        let root = workspace_root.to_path_buf();
        move || {
            std::process::Command::new("cargo")
                .args(&build_args)
                .current_dir(&root)
                .output()
        }
    })
    .await
    .map_err(|e| format!("build task panicked: {e}"))?
    .map_err(|e| format!("failed to run cargo build: {e}"))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        crate::slog!("[gateway-rebuild] Build failed");
        return Err(format!("Build failed:\n{stderr}"));
    }

    crate::slog!("[gateway-rebuild] Build succeeded — launching trampoline");

    // Paths for the new and old binaries.
    let new_binary = if cfg!(debug_assertions) {
        workspace_root.join("target/debug/huskies")
    } else {
        workspace_root.join("target/release/huskies")
    };

    let old_binary =
        std::env::current_exe().map_err(|e| format!("cannot locate current binary: {e}"))?;

    let huskies_dir = config_dir.join(".huskies");
    std::fs::create_dir_all(&huskies_dir)
        .map_err(|e| format!("cannot create .huskies dir: {e}"))?;
    let backup_binary = huskies_dir.join("huskies_backup");

    // Current gateway args (skip argv[0]).
    let gateway_args: Vec<String> = std::env::args().skip(1).collect();

    let job = crate::trampoline::TrampolineJob {
        gateway_pid: std::process::id(),
        new_binary_path: new_binary,
        old_binary_path: old_binary,
        backup_binary_path: backup_binary,
        gateway_args,
        health_url: format!("http://127.0.0.1:{gateway_port}/api/gateway"),
    };

    let job_path = huskies_dir.join("trampoline.json");
    crate::trampoline::write_job_atomic(&job, &job_path)?;

    let exe = std::env::current_exe()
        .map_err(|e| format!("cannot locate current binary for trampoline: {e}"))?;
    crate::trampoline::spawn_detached_trampoline(&exe, &job_path)?;

    crate::slog!("[gateway-rebuild] Trampoline launched — gateway will be replaced shortly");
    Ok(())
}
