// Integration coverage for `lesavka-uvc` process startup parsing.
//
// Scope: launch the real `lesavka-uvc` binary with controlled arguments and
// environment overrides to exercise argument/config parsing and early startup.
// Targets: `server/src/bin/lesavka-uvc.rs`.
// Why: command-line/environment startup behavior should fail fast and remain
// deterministic without a physical UVC gadget node in CI.

use serial_test::serial;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, ExitStatus};
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;

fn candidate_dirs() -> Vec<PathBuf> {
    let exe = std::env::current_exe().expect("current exe path");
    let mut dirs = Vec::new();
    if let Some(parent) = exe.parent() {
        dirs.push(parent.to_path_buf());
        if let Some(grand) = parent.parent() {
            dirs.push(grand.to_path_buf());
        }
    }
    dirs.push(PathBuf::from("target/debug"));
    dirs.push(PathBuf::from("target/llvm-cov-target/debug"));
    dirs
}

fn find_binary(name: &str) -> Option<PathBuf> {
    candidate_dirs()
        .into_iter()
        .map(|dir| dir.join(name))
        .find(|path| path.exists() && path.is_file())
}

fn wait_for_exit(mut child: Child, timeout: Duration) -> ExitStatus {
    let deadline = Instant::now() + timeout;
    loop {
        if let Some(status) = child.try_wait().expect("poll child") {
            return status;
        }
        if Instant::now() >= deadline {
            let _ = child.kill();
            let _ = child.wait();
            panic!("lesavka-uvc did not exit within timeout");
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

#[test]
#[serial]
fn uvc_binary_requires_device_argument_or_env() {
    let Some(bin) = find_binary("lesavka-uvc") else {
        return;
    };

    let status = Command::new(Path::new(&bin))
        .env_remove("LESAVKA_UVC_DEV")
        .status()
        .expect("spawn lesavka-uvc");
    assert!(
        !status.success(),
        "uvc binary should fail without a device path"
    );
}

#[test]
#[serial]
fn uvc_binary_applies_env_config_and_fails_fast_on_non_v4l2_node() {
    let Some(bin) = find_binary("lesavka-uvc") else {
        return;
    };

    let fake_device = NamedTempFile::new().expect("temp device");
    let lock = NamedTempFile::new().expect("temp lock");
    let child = Command::new(Path::new(&bin))
        .arg("--device")
        .arg(fake_device.path())
        .env("LESAVKA_UVC_LOCK_PATH", lock.path())
        .env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "256")
        .env("LESAVKA_UVC_MAXPACKET", "4096")
        .env("LESAVKA_UVC_BULK", "1")
        .env("LESAVKA_UVC_FPS", "30")
        .spawn()
        .expect("spawn lesavka-uvc");

    let status = wait_for_exit(child, Duration::from_secs(3));
    assert!(
        !status.success(),
        "uvc binary should fail on non-v4l2 test file"
    );
}

#[test]
#[serial]
fn uvc_binary_accepts_positional_device_argument() {
    let Some(bin) = find_binary("lesavka-uvc") else {
        return;
    };

    let fake_device = NamedTempFile::new().expect("temp device");
    let lock = NamedTempFile::new().expect("temp lock");
    let child = Command::new(Path::new(&bin))
        .arg(fake_device.path())
        .env("LESAVKA_UVC_LOCK_PATH", lock.path())
        .env_remove("LESAVKA_UVC_BULK")
        .env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "2048")
        .env("LESAVKA_UVC_MAXPACKET", "1024")
        .spawn()
        .expect("spawn lesavka-uvc");

    let status = wait_for_exit(child, Duration::from_secs(3));
    assert!(
        !status.success(),
        "uvc binary should fail on non-v4l2 test file"
    );
}
