#[cfg(coverage)]
/// Returns the optional JSON stats path for the UVC coverage helper.
///
/// Inputs: `LESAVKA_UVC_STATS_PATH`. Output: path or disabled state. Why:
/// tests and probes need a cheap health artifact without forcing writes.
fn uvc_stats_path() -> Option<std::path::PathBuf> {
    match std::env::var("LESAVKA_UVC_STATS_PATH") {
        Ok(value) => {
            let trimmed = value.trim();
            if trimmed.is_empty() || trimmed == "0" {
                None
            } else {
                Some(std::path::PathBuf::from(trimmed))
            }
        }
        Err(_) => Some(std::path::PathBuf::from(DEFAULT_UVC_STATS_PATH)),
    }
}

#[cfg(coverage)]
/// Writes a text artifact through a temporary file and atomic rename.
///
/// Inputs: target path and payload. Output: persisted file. Why: readers should
/// never observe partially-written UVC stats JSON.
fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    let tmp = path.with_extension(format!("tmp.{}", std::process::id()));
    std::fs::write(&tmp, text)?;
    std::fs::rename(&tmp, path)?;
    Ok(())
}

#[cfg(coverage)]
/// Serializes the compact UVC video-stat snapshot consumed by probes.
///
/// Inputs: current counters and frame cap. Output: one JSON object string. Why:
/// shell probes need stable fields without depending on Rust serialization.
fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String {
    let kernel = uvc_kernel_stats_json();
    format!(
        "{{\"generated_unix_ms\":0,\"queued\":{},\"reloaded\":{},\"stale_replay\":{},\"rejected_oversize\":{},\"rejected_invalid\":{},\"fallback_idle\":{},\"held_last_good\":{},\"read_errors\":{},\"strict_validation_failures\":{},\"dqbuf_ioctl_errors\":{},\"dqbuf_flag_errors\":{},\"qbuf_ioctl_errors\":{},\"latest_bytes\":{},\"frame_cap\":{},\"last_rejected_oversize_bytes\":{},\"last_rejected_oversize_cap\":{},\"paced_sleeps\":{},\"paced_sleep_ms\":{},\"kernel\":{}}}\n",
        stats.queued,
        stats.reloaded,
        stats.replayed_stale,
        stats.rejected_oversize,
        stats.rejected_invalid,
        stats.fallback_idle,
        stats.held_last_good,
        stats.read_errors,
        stats.strict_validation_failures,
        stats.dqbuf_ioctl_errors,
        stats.dqbuf_flag_errors,
        stats.qbuf_ioctl_errors,
        stats.latest_bytes,
        frame_cap,
        stats.last_rejected_oversize_bytes,
        stats.last_rejected_oversize_cap,
        stats.paced_sleeps,
        stats.paced_sleep_ms,
        kernel
    )
}

#[cfg(coverage)]
fn uvc_kernel_stats_json() -> String {
    let path = std::env::var("LESAVKA_UVC_KERNEL_STATS_PATH")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|_| std::path::PathBuf::from(DEFAULT_UVC_KERNEL_STATS_PATH));
    std::fs::read_to_string(path)
        .ok()
        .and_then(|value| serde_json::from_str::<serde_json::Value>(&value).ok())
        .map(|value| value.to_string())
        .unwrap_or_else(|| "null".to_string())
}

#[cfg(coverage)]
/// Returns the optional periodic stats write interval.
///
/// Inputs: `LESAVKA_UVC_STATS_INTERVAL_MS`. Output: duration or disabled state.
/// Why: probes can ask for telemetry without slowing every coverage test.
fn uvc_stats_interval() -> Option<std::time::Duration> {
    match env_u64("LESAVKA_UVC_STATS_INTERVAL_MS", DEFAULT_UVC_STATS_INTERVAL_MS) {
        0 => None,
        value => Some(std::time::Duration::from_millis(value)),
    }
}

#[cfg(coverage)]
/// Returns the optional maximum age for a spooled MJPEG frame.
///
/// Inputs: `LESAVKA_UVC_FRAME_MAX_AGE_MS`. Outputs: `None` when the TTL is
/// disabled. Why: stale-frame replay must be explicit because it trades
/// smoothness against freshness during browser stream recovery.
fn frame_spool_max_age() -> Option<std::time::Duration> {
    match env_u64(
        "LESAVKA_UVC_FRAME_MAX_AGE_MS",
        DEFAULT_UVC_FRAME_MAX_AGE_MS,
    ) {
        0 => None,
        value => Some(std::time::Duration::from_millis(value)),
    }
}

#[cfg(coverage)]
/// Determines whether a spooled MJPEG frame is too old to replay.
///
/// Inputs: frame path and optional age limit. Outputs: true when the frame is
/// missing or older than the limit. Why: the coverage harness should guard the
/// same freshness cut-off that prevents seconds-old video from re-entering UVC.
fn frame_spool_is_stale(path: &std::path::Path, max_age: Option<std::time::Duration>) -> bool {
    let Some(max_age) = max_age else {
        return false;
    };
    let Ok(metadata) = std::fs::metadata(path) else {
        return true;
    };
    let Ok(modified) = metadata.modified() else {
        return false;
    };
    std::time::SystemTime::now()
        .duration_since(modified)
        .map(|age| age > max_age)
        .unwrap_or(false)
}

#[cfg(coverage)]
fn frame_spool_modified(path: &std::path::Path) -> Option<std::time::SystemTime> {
    std::fs::metadata(path).ok()?.modified().ok()
}

#[cfg(coverage)]
fn verified_frame_idle_timeout_elapsed(modified: Option<std::time::SystemTime>) -> bool {
    let Some(modified) = modified else {
        return true;
    };
    std::time::SystemTime::now()
        .duration_since(modified)
        .map(|age| {
            age >= std::time::Duration::from_millis(
                std::env::var("LESAVKA_UVC_IDLE_AFTER_MS")
                    .ok()
                    .and_then(|value| value.parse::<u64>().ok())
                    .unwrap_or(DEFAULT_UVC_IDLE_AFTER_STALE_MS),
            )
        })
        .unwrap_or(false)
}
