use super::*;
use crate::launcher::{
    devices::{CameraMode, DeviceCatalog},
    diagnostics::PerformanceSample,
    preview::PreviewBinding,
    state::{
        BreakoutSizePreset, LauncherState, PreviewSourceSize, UpstreamAudioTransport,
        WebcamTransport,
    },
    ui_components::build_launcher_view,
};
use crate::uplink_telemetry::UpstreamStreamTelemetry;
use gtk::prelude::*;
use serial_test::serial;
use std::{
    cell::RefCell,
    collections::BTreeMap,
    fs,
    path::PathBuf,
    process,
    rc::Rc,
    time::{Duration, Instant},
};

const LAUNCHER_1080P_WIDTH_BUDGET: i32 = 1_640;

fn present_and_settle(window: &gtk::ApplicationWindow) {
    window.present();
    let deadline = Instant::now() + Duration::from_millis(450);
    while Instant::now() < deadline {
        while glib::MainContext::default().iteration(false) {}
        std::thread::sleep(Duration::from_millis(15));
    }
}

fn rail_button_text(button: &gtk::Button) -> Option<String> {
    button
        .child()
        .and_then(|child| child.downcast::<gtk::Label>().ok())
        .map(|label| label.text().to_string())
}

fn unique_test_path(label: &str) -> PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("system clock before unix epoch")
        .as_nanos();
    std::env::temp_dir().join(format!(
        "lesavka-relay-child-{label}-{}-{nanos}",
        process::id()
    ))
}

fn unique_test_file(label: &str) -> PathBuf {
    let path = unique_test_path(label);
    fs::write(&path, b"test executable placeholder").expect("write relay child test file");
    path
}

fn unique_deleted_exe_path(label: &str) -> PathBuf {
    let path = unique_test_path(label);
    let deleted_path = path.with_file_name(format!(
        "{} (deleted)",
        path.file_name()
            .expect("test path has file name")
            .to_string_lossy()
    ));
    fs::write(&deleted_path, b"test executable placeholder")
        .expect("write deleted relay child test file");
    deleted_path
}

#[test]
fn relay_child_exe_uses_current_exe_when_still_runnable() {
    let current = unique_test_file("current");
    let installed = unique_test_file("installed");

    let resolved = resolve_relay_child_exe_from(None, &current, &installed)
        .expect("resolve relay child executable");

    assert_eq!(resolved, current);
}

#[test]
fn relay_child_exe_falls_back_when_launcher_exe_was_replaced() {
    let current = unique_deleted_exe_path("current");
    let installed = unique_test_file("installed");

    let resolved = resolve_relay_child_exe_from(None, &current, &installed)
        .expect("resolve relay child executable");

    assert_eq!(resolved, installed);
}

#[test]
fn relay_child_exe_falls_back_when_current_exe_path_is_missing() {
    let current = unique_test_path("missing-current");
    let installed = unique_test_file("installed");

    let resolved = resolve_relay_child_exe_from(None, &current, &installed)
        .expect("resolve relay child executable");

    assert_eq!(resolved, installed);
}

#[test]
fn relay_child_exe_honors_valid_env_override() {
    let override_path = unique_test_file("override");
    let current = unique_test_file("current");
    let installed = unique_test_file("installed");

    let resolved = resolve_relay_child_exe_from(Some(&override_path), &current, &installed)
        .expect("resolve relay child executable");

    assert_eq!(resolved, override_path);
}

#[test]
fn relay_child_exe_rejects_invalid_env_override() {
    let override_path = unique_test_path("missing-override");
    let current = unique_test_file("current");
    let installed = unique_test_file("installed");

    let err = resolve_relay_child_exe_from(Some(&override_path), &current, &installed)
        .expect_err("invalid override should fail loudly");

    assert!(err.to_string().contains(CLIENT_BIN_ENV));
}

#[test]
fn local_test_detail_mentions_idle_and_running_modes() {
    assert!(local_test_detail(false, false, false, false).contains("idle"));
    let running = local_test_detail(true, true, false, false);
    assert!(running.contains("camera preview"));
    assert!(running.contains("mic monitor"));
}

#[test]
fn gpio_power_label_tracks_detected_devices() {
    let mut power = CapturePowerStatus::default();
    assert_eq!(gpio_power_label(&power), "Off");

    power.available = true;
    assert_eq!(gpio_power_label(&power), "Off");

    power.enabled = true;
    assert_eq!(gpio_power_label(&power), "On");

    power.detected_devices = 1;
    assert_eq!(gpio_power_label(&power), "On");

    power.detected_devices = 2;
    assert_eq!(gpio_power_label(&power), "On");
}

fn eye_health_sample() -> PerformanceSample {
    PerformanceSample {
        rtt_ms: 20.0,
        probe_spread_ms: 2.0,
        input_latency_ms: 12.0,
        probe_loss_pct: 0.0,
        client_process_cpu_pct: 10.0,
        server_process_cpu_pct: 8.0,
        video_loss_pct: 0.0,
        left_receive_fps: 30.0,
        left_present_fps: 30.0,
        left_server_fps: 30.0,
        left_stream_spread_ms: 3.0,
        left_packet_gap_peak_ms: 20.0,
        left_present_gap_peak_ms: 20.0,
        left_queue_depth: 0,
        left_queue_peak: 0,
        left_server_source_gap_peak_ms: 20.0,
        left_server_send_gap_peak_ms: 20.0,
        left_server_queue_peak: 0,
        left_server_encoder_label: "source-pass-through".to_string(),
        left_decoder_label: "nvidia-h264".to_string(),
        left_stream_caps_label: "video/x-h264".to_string(),
        left_decoded_caps_label: "video/x-raw".to_string(),
        left_rendered_caps_label: "video/x-raw".to_string(),
        right_receive_fps: 30.0,
        right_present_fps: 30.0,
        right_server_fps: 30.0,
        right_stream_spread_ms: 3.0,
        right_packet_gap_peak_ms: 20.0,
        right_present_gap_peak_ms: 20.0,
        right_queue_depth: 0,
        right_queue_peak: 0,
        right_server_source_gap_peak_ms: 20.0,
        right_server_send_gap_peak_ms: 20.0,
        right_server_queue_peak: 0,
        right_server_encoder_label: "source-pass-through".to_string(),
        right_decoder_label: "nvidia-h264".to_string(),
        right_stream_caps_label: "video/x-h264".to_string(),
        right_decoded_caps_label: "video/x-raw".to_string(),
        right_rendered_caps_label: "video/x-raw".to_string(),
        upstream_camera: UpstreamStreamTelemetry::default(),
        upstream_microphone: UpstreamStreamTelemetry::default(),
        upstream_mode: "bundled-webcam-media".to_string(),
        dropped_frames: 0,
        queue_depth: 0,
    }
}

#[test]
fn eye_stream_chip_reports_per_eye_fps_quality() {
    let sample = eye_health_sample();
    assert_eq!(
        eye_stream_health(Some(&sample), EyeChipSide::Left, false),
        (
            StatusLightState::Idle,
            "Off".to_string(),
            "Relay is not connected.".to_string()
        )
    );

    let (state, label, tooltip) = eye_stream_health(Some(&sample), EyeChipSide::Left, true);
    assert_eq!(state, StatusLightState::Connected);
    assert_eq!(label, "30 fps");
    assert!(tooltip.contains("present=30.0 fps"));

    let mut degraded = sample.clone();
    degraded.right_present_fps = 20.0;
    degraded.right_queue_peak = 3;
    let (state, label, tooltip) = eye_stream_health(Some(&degraded), EyeChipSide::Right, true);
    assert_eq!(state, StatusLightState::Caution);
    assert_eq!(label, "20 fps");
    assert!(tooltip.contains("queue=0/3"));

    let mut broken = sample;
    broken.left_receive_fps = 0.0;
    broken.left_present_fps = 0.0;
    let (state, label, _) = eye_stream_health(Some(&broken), EyeChipSide::Left, true);
    assert_eq!(state, StatusLightState::Warning);
    assert_eq!(label, "0 fps");
}

#[gtk::test]
#[serial]
fn launcher_shell_measures_inside_a_1080p_desktop_budget() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-layout-budget")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let state = LauncherState::new();
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state,
    );
    present_and_settle(&view.window);

    let (min_width, natural_width, _, _) = view.window.measure(gtk::Orientation::Horizontal, -1);
    let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920);

    assert!(
        min_width <= LAUNCHER_1080P_WIDTH_BUDGET
            && view.window.width() <= LAUNCHER_1080P_WIDTH_BUDGET,
        "launcher width budget regressed: min={min_width}, natural={natural_width}"
    );
    assert!(
        min_height <= 980 && view.window.height() <= 980,
        "launcher height budget regressed: min={min_height}, natural={natural_height}"
    );
}

#[gtk::test]
#[serial]
fn populated_launcher_shell_measures_inside_a_1080p_desktop_budget() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-layout-budget-populated")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let catalog = realistic_device_catalog();
    let mut state = LauncherState::new();
    state.apply_catalog_defaults(&catalog);

    let view = build_launcher_view(&app, "http://127.0.0.1:50051", &catalog, &state);
    present_and_settle(&view.window);

    let (min_width, natural_width, _, _) = view.window.measure(gtk::Orientation::Horizontal, -1);
    let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920);

    assert!(
        min_width <= LAUNCHER_1080P_WIDTH_BUDGET
            && view.window.width() <= LAUNCHER_1080P_WIDTH_BUDGET,
        "populated launcher width budget regressed: min={min_width}, natural={natural_width}"
    );
    assert!(
        min_height <= 980 && view.window.height() <= 980,
        "populated launcher height budget regressed: min={min_height}, natural={natural_height}"
    );
}

#[gtk::test]
#[serial]
fn populated_launcher_runtime_widgets_stay_compact() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-layout-widget-budget")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let catalog = realistic_device_catalog();
    let mut state = LauncherState::new();
    state.apply_catalog_defaults(&catalog);

    let view = build_launcher_view(&app, "http://127.0.0.1:50051", &catalog, &state);
    present_and_settle(&view.window);

    let (camera_min_w, camera_nat_w, _, _) = view
        .device_stage
        .camera_preview
        .measure(gtk::Orientation::Horizontal, -1);
    let (camera_min_h, camera_nat_h, _, _) = view
        .device_stage
        .camera_preview
        .measure(gtk::Orientation::Vertical, 520);
    let (testing_panel_min_h, testing_panel_nat_h, _, _) = view
        .device_stage
        .preview_panel
        .measure(gtk::Orientation::Vertical, 520);
    let (left_min_w, left_nat_w, _, _) = view.widgets.display_panes[0]
        .root
        .measure(gtk::Orientation::Horizontal, -1);
    let (left_min_h, left_nat_h, _, _) = view.widgets.display_panes[0]
        .root
        .measure(gtk::Orientation::Vertical, 720);
    let (server_min_w, server_nat_w, _, _) =
        view.server_entry.measure(gtk::Orientation::Horizontal, -1);
    let (device_panel_min_w, device_panel_nat_w, _, _) = view
        .device_stage
        .devices_panel
        .measure(gtk::Orientation::Horizontal, -1);

    assert!(
        camera_min_w >= 480 && camera_nat_w >= 480,
        "camera preview width regressed: min={camera_min_w}, natural={camera_nat_w}"
    );
    assert!(
        camera_min_h >= 270 && camera_nat_h >= 270,
        "camera preview height regressed: min={camera_min_h}, natural={camera_nat_h}"
    );
    assert!(
        testing_panel_min_h <= 465 && testing_panel_nat_h <= 465,
        "device testing panel height regressed: min={testing_panel_min_h}, natural={testing_panel_nat_h}"
    );
    let device_panel_width = view.device_stage.devices_panel.width();
    assert!(
        device_panel_min_w <= 610 && device_panel_width <= 610,
        "device staging should leave width for upstream media: min={device_panel_min_w}, natural={device_panel_nat_w}, allocated={device_panel_width}"
    );
    assert!(
        left_min_w <= 700 && left_nat_w <= 720,
        "eye pane width regressed: min={left_min_w}, natural={left_nat_w}"
    );
    assert!(
        left_min_h <= 700 && left_nat_h <= 700,
        "eye pane height regressed: min={left_min_h}, natural={left_nat_h}"
    );
    assert!(
        server_min_w <= 168 && server_nat_w <= 180,
        "server entry width regressed: min={server_min_w}, natural={server_nat_w}"
    );
    assert!(
        (view.device_stage.devices_panel.height() - view.device_stage.preview_panel.height()).abs()
            <= 2,
        "device staging and upstream media heights diverged: staging={}, upstream={}",
        view.device_stage.devices_panel.height(),
        view.device_stage.preview_panel.height()
    );
    assert_eq!(
        view.device_stage
            .camera_preview_stack
            .visible_child_name()
            .as_deref(),
        Some("idle"),
        "idle launcher should show the placeholder webcam surface until preview wakes up"
    );
    view.device_stage
        .camera_preview_stack
        .set_visible_child_name("live");
    present_and_settle(&view.window);
    assert!(
        view.widgets.display_panes[0].preview_frame.height()
            >= view.device_stage.camera_preview_frame.height(),
        "eye preview should stay at least as tall as the webcam preview: eye={}, webcam={}",
        view.widgets.display_panes[0].preview_frame.height(),
        view.device_stage.camera_preview_frame.height()
    );
    assert!(
        view.widgets.display_panes[0].preview_frame.width()
            >= view.device_stage.camera_preview_frame.width(),
        "eye preview should stay at least as wide as the webcam preview: eye={}, webcam={}",
        view.widgets.display_panes[0].preview_frame.width(),
        view.device_stage.camera_preview_frame.width()
    );
    assert!(
        view.widgets.display_panes[0].root.width()
            - view.widgets.display_panes[0].preview_frame.width()
            <= 60,
        "eye preview stopped filling its card width: card={}, preview={}",
        view.widgets.display_panes[0].root.width(),
        view.widgets.display_panes[0].preview_frame.width()
    );
    assert!(
        view.device_stage.camera_preview_frame.height() >= 270,
        "webcam preview should use the taller upstream media space: {}",
        view.device_stage.camera_preview_frame.height()
    );
    assert!(
        view.device_stage.preview_panel.width() > view.device_stage.devices_panel.width(),
        "upstream media should receive extra width: staging={}, upstream={}",
        view.device_stage.devices_panel.width(),
        view.device_stage.preview_panel.width()
    );
}

#[gtk::test]
#[serial]
fn relay_connect_button_aligns_with_recovery_video_column() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-relay-row-alignment")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let view = build_launcher_view(
        &app,
        "https://38.28.125.112:50051",
        &realistic_device_catalog(),
        &LauncherState::new(),
    );
    present_and_settle(&view.window);

    let connect_bounds = view
        .widgets
        .start_button
        .compute_bounds(&view.window)
        .expect("connect button bounds");
    let video_bounds = view
        .widgets
        .uvc_recover_button
        .compute_bounds(&view.window)
        .expect("video recovery button bounds");

    assert!(
        (connect_bounds.x() - video_bounds.x()).abs() <= 8.0,
        "Connect should visually align with the Video recovery column: connect_x={} video_x={}",
        connect_bounds.x(),
        video_bounds.x()
    );
    assert!(
        connect_bounds.width() >= video_bounds.width() - 2.0,
        "Connect should be at least as stable as the Video recovery button: connect_w={} video_w={}",
        connect_bounds.width(),
        video_bounds.width()
    );
}

#[gtk::test]
#[serial]
fn launcher_shell_installs_native_window_chrome() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-window-chrome")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &LauncherState::new(),
    );
    present_and_settle(&view.window);

    let titlebar = view
        .window
        .titlebar()
        .expect("launcher titlebar")
        .downcast::<gtk::HeaderBar>()
        .expect("launcher header bar");
    assert!(view.window.is_decorated());
    assert!(titlebar.shows_title_buttons());
}

#[gtk::test]
#[serial]
fn recovery_buttons_use_upstream_device_labels() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-recovery-labels")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &LauncherState::new(),
    );

    assert_eq!(
        rail_button_text(&view.widgets.usb_recover_button),
        Some("HID".to_string())
    );
    assert_eq!(
        rail_button_text(&view.widgets.uac_recover_button),
        Some("Audio".to_string())
    );
    assert_eq!(
        rail_button_text(&view.widgets.uvc_recover_button),
        Some("Video".to_string())
    );
    assert!(
        view.widgets
            .usb_recover_button
            .tooltip_text()
            .as_deref()
            .unwrap_or_default()
            .contains("keyboard/mouse HID")
    );
    assert!(
        view.widgets
            .uac_recover_button
            .tooltip_text()
            .as_deref()
            .unwrap_or_default()
            .contains("bundled mic+camera")
    );
    assert!(
        view.widgets
            .uvc_recover_button
            .tooltip_text()
            .as_deref()
            .unwrap_or_default()
            .contains("virtual webcam spool")
    );
}

#[gtk::test]
#[serial]
fn webcam_transport_combo_tracks_selected_upstream_codec() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-webcam-transport")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let mut state = LauncherState::new();
    state.set_camera_channel_enabled(true);
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state,
    );

    assert_eq!(
        view.device_stage
            .webcam_transport_combo
            .active_id()
            .as_deref(),
        Some("mjpeg")
    );
    assert!(view.device_stage.webcam_transport_combo.is_sensitive());

    state.select_webcam_transport(WebcamTransport::Hevc);
    refresh_launcher_ui(&view.widgets, &state, false);
    assert_eq!(
        view.device_stage
            .webcam_transport_combo
            .active_id()
            .as_deref(),
        Some("hevc")
    );
    assert!(view.device_stage.webcam_transport_combo.is_sensitive());

    refresh_launcher_ui(&view.widgets, &state, true);
    assert!(view.device_stage.webcam_transport_combo.is_sensitive());
    assert!(
        view.device_stage
            .webcam_transport_combo
            .tooltip_text()
            .as_deref()
            .unwrap_or_default()
            .contains("restarts the live camera path")
    );
}

#[test]
fn audio_transport_model_defaults_to_pcm_with_compressed_opt_in() {
    assert_eq!(
        UpstreamAudioTransport::default(),
        UpstreamAudioTransport::Pcm
    );
    assert_eq!(UpstreamAudioTransport::Opus.as_id(), "opus");
    assert_eq!(UpstreamAudioTransport::Opus.label(), "Opus");
    assert_eq!(UpstreamAudioTransport::Pcm.as_id(), "pcm");
    assert_eq!(UpstreamAudioTransport::Pcm.label(), "PCM");
    assert_eq!(
        UpstreamAudioTransport::from_id("raw"),
        Some(UpstreamAudioTransport::Pcm)
    );
    assert_eq!(
        UpstreamAudioTransport::from_id("compressed"),
        Some(UpstreamAudioTransport::Opus)
    );
}

#[gtk::test]
#[serial]
fn diagnostics_and_log_popouts_install_native_window_chrome() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-popout-chrome")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let diagnostics_handle = Rc::new(RefCell::new(None::<gtk::ApplicationWindow>));
    let diagnostics_label = Rc::new(RefCell::new(None::<gtk::Label>));
    let diagnostics_scroll = Rc::new(RefCell::new(None::<gtk::ScrolledWindow>));
    let rendered_text = Rc::new(RefCell::new("diagnostics".to_string()));

    open_diagnostics_popout(
        &app,
        &diagnostics_handle,
        &diagnostics_label,
        &diagnostics_scroll,
        &rendered_text,
    );

    let diagnostics_window = diagnostics_handle
        .borrow()
        .as_ref()
        .expect("diagnostics window")
        .clone();
    let diagnostics_titlebar = diagnostics_window
        .titlebar()
        .expect("diagnostics titlebar")
        .downcast::<gtk::HeaderBar>()
        .expect("diagnostics header bar");
    assert!(diagnostics_window.is_decorated());
    assert!(diagnostics_titlebar.shows_title_buttons());

    let log_handle = Rc::new(RefCell::new(None::<gtk::ApplicationWindow>));
    let log_scroll = Rc::new(RefCell::new(None::<gtk::ScrolledWindow>));
    let log_buffer = gtk::TextBuffer::new(None);
    log_buffer.set_text("session log");

    open_session_log_popout(&app, &log_handle, &log_buffer);

    let log_window = log_handle.borrow().as_ref().expect("log window").clone();
    let log_titlebar = log_window
        .titlebar()
        .expect("log titlebar")
        .downcast::<gtk::HeaderBar>()
        .expect("log header bar");
    assert!(log_window.is_decorated());
    assert!(log_titlebar.shows_title_buttons());

    diagnostics_window.close();
    log_window.close();
    diagnostics_label.borrow_mut().take();
    diagnostics_scroll.borrow_mut().take();
    log_scroll.borrow_mut().take();
}

#[gtk::test]
#[serial]
fn breakout_size_changes_resize_the_open_popout_window() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-breakout-resize")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let window = gtk::ApplicationWindow::builder()
        .application(&app)
        .default_width(540)
        .default_height(304)
        .build();
    let root = gtk::Box::new(gtk::Orientation::Vertical, 0);
    let frame = gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false);
    let picture = gtk::Picture::new();
    frame.set_child(Some(&picture));
    root.append(&frame);
    window.set_child(Some(&root));

    let handle = PopoutWindowHandle {
        window,
        root,
        frame: frame.clone(),
        picture: picture.clone(),
        status_label: gtk::Label::new(None),
        binding: PreviewBinding::test_stub(),
    };

    apply_popout_window_size(
        &handle,
        BreakoutSizeChoice {
            preset: BreakoutSizePreset::P720,
            width: 1280,
            height: 720,
        },
        PreviewSourceSize {
            width: 1920,
            height: 1080,
            fps: 60,
        },
    );

    assert_eq!(handle.root.width_request(), 1280);
    assert_eq!(handle.root.height_request(), 720);
    assert_eq!(handle.frame.width_request(), 1280);
    assert_eq!(handle.frame.height_request(), 720);
    assert_eq!(handle.picture.width_request(), 1280);
    assert_eq!(handle.picture.height_request(), 720);
}

#[test]
fn server_chip_state_tracks_connection_not_just_reachability() {
    let mut state = LauncherState::new();
    assert_eq!(server_light_state(&state, false), StatusLightState::Idle);
    assert_eq!(server_version_label(&state), "???");

    state.set_server_available(true);
    state.set_server_version(Some(crate::VERSION.to_string()));
    assert_eq!(server_light_state(&state, false), StatusLightState::Live);
    assert_eq!(server_version_label(&state), format!("v{}", crate::VERSION));

    assert_eq!(
        server_light_state(&state, true),
        StatusLightState::Connected
    );

    state.set_server_version(Some("v0.12.5".to_string()));
    assert_eq!(server_light_state(&state, false), StatusLightState::Warning);
    assert_eq!(server_light_state(&state, true), StatusLightState::Caution);
    assert_eq!(server_version_label(&state), "v0.12.5");

    state.set_server_version(Some("  ".to_string()));
    assert_eq!(server_light_state(&state, false), StatusLightState::Idle);
    assert_eq!(server_version_label(&state), "???");
}

#[test]
fn hid_chip_uses_live_relay_when_capability_probe_is_late() {
    let mut state = LauncherState::new();
    state.set_server_available(true);

    assert_eq!(
        recovery_usb_health(&state, false),
        (StatusLightState::Caution, "Unknown".to_string())
    );
    assert_eq!(
        recovery_usb_health(&state, true),
        (StatusLightState::Live, "Connected".to_string())
    );
}

#[test]
fn uac_chip_uses_live_microphone_flow_not_only_server_caps() {
    let mut state = LauncherState::new();
    state.set_server_available(true);
    state.set_server_media_caps(Some(true), Some(true), Some("uvc".to_string()), None);
    state.set_microphone_channel_enabled(true);

    assert_eq!(
        recovery_uac_health(&state, false, None),
        (StatusLightState::Live, "PCM".to_string())
    );
    assert_eq!(
        recovery_uac_health(&state, true, None),
        (StatusLightState::Caution, "No Flow".to_string())
    );

    let healthy = UpstreamStreamTelemetry {
        enabled: true,
        connected: true,
        packets_streamed: 24,
        latest_delivery_age_ms: 42.0,
        latest_enqueue_age_ms: 12.0,
        queue_depth: 1,
        ..UpstreamStreamTelemetry::default()
    };
    assert_eq!(
        recovery_uac_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "PCM".to_string())
    );

    state.set_server_media_caps(None, None, None, None);
    assert_eq!(
        recovery_uac_health(&state, false, Some(&healthy)),
        (StatusLightState::Caution, "Unknown".to_string())
    );
    assert_eq!(
        recovery_uac_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "PCM".to_string())
    );

    state.select_upstream_audio_transport(UpstreamAudioTransport::Pcm);
    assert_eq!(
        recovery_uac_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "PCM".to_string())
    );

    state.set_microphone_channel_enabled(false);
    assert_eq!(
        recovery_uac_health(&state, true, Some(&healthy)),
        (StatusLightState::Idle, "Paused".to_string())
    );
}

#[test]
fn uvc_chip_degrades_when_live_camera_frames_are_not_flowing() {
    let mut state = LauncherState::new();
    state.set_server_available(true);
    state.set_server_media_caps(
        Some(true),
        Some(true),
        Some("uvc".to_string()),
        Some("mjpeg".to_string()),
    );
    state.set_camera_channel_enabled(true);

    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Live, "MJPEG".to_string())
    );
    assert_eq!(
        recovery_uvc_health(&state, true, None),
        (StatusLightState::Caution, "No Frames".to_string())
    );

    let healthy = UpstreamStreamTelemetry {
        enabled: true,
        connected: true,
        packets_streamed: 12,
        latest_delivery_age_ms: 48.0,
        latest_enqueue_age_ms: 20.0,
        queue_depth: 3,
        ..UpstreamStreamTelemetry::default()
    };
    assert_eq!(
        recovery_uvc_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "MJPEG".to_string())
    );

    state.set_server_media_caps(None, None, None, None);
    assert_eq!(
        recovery_uvc_health(&state, false, Some(&healthy)),
        (StatusLightState::Caution, "Unknown".to_string())
    );
    assert_eq!(
        recovery_uvc_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "MJPEG".to_string())
    );

    state.select_webcam_transport(WebcamTransport::Mjpeg);
    assert_eq!(
        recovery_uvc_health(&state, true, Some(&healthy)),
        (StatusLightState::Live, "MJPEG".to_string())
    );

    let lagging = UpstreamStreamTelemetry {
        latest_delivery_age_ms: 321.0,
        ..healthy
    };
    assert_eq!(
        recovery_uvc_health(&state, true, Some(&lagging)),
        (StatusLightState::Caution, "Lagging".to_string())
    );
}

#[test]
fn uvc_chip_uses_helper_integrity_and_payload_contract_evidence() {
    let mut state = LauncherState::new();
    state.set_server_available(true);
    state.set_server_media_caps(
        Some(true),
        Some(true),
        Some("uvc".to_string()),
        Some("mjpeg".to_string()),
    );
    state.upstream_sync.available = true;
    state.upstream_sync.uvc_contract_status = "ready".to_string();
    state.upstream_sync.uvc_contract_divergent = false;
    state.upstream_sync.uvc_integrity_status = "ready".to_string();
    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Live, "MJPEG".to_string())
    );

    state.upstream_sync.uvc_integrity_status = "holding".to_string();
    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Caution, "Holding".to_string())
    );
    state.upstream_sync.uvc_integrity_status = "fault".to_string();
    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Warning, "Integrity fault".to_string())
    );

    state.upstream_sync.uvc_integrity_status = "ready".to_string();
    state.upstream_sync.uvc_contract_status = "unverified".to_string();
    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Caution, "Unverified".to_string())
    );
    state.upstream_sync.uvc_contract_status = "divergent".to_string();
    state.upstream_sync.uvc_contract_divergent = true;
    assert_eq!(
        recovery_uvc_health(&state, false, None),
        (StatusLightState::Warning, "Contract fault".to_string())
    );
}

#[test]
fn capture_power_detail_mentions_detected_eyes_when_powered() {
    let power = CapturePowerStatus {
        available: true,
        enabled: true,
        detail: "active/running".to_string(),
        detected_devices: 1,
        ..Default::default()
    };

    assert!(capture_power_detail(&power).contains("1 eye detected"));
}

#[test]
fn compact_device_name_prefers_basename_when_available() {
    assert_eq!(compact_device_name("/dev/video0"), "video0");
    assert_eq!(compact_device_name("alsa_input.usb"), "alsa_input.usb");
}

#[test]
fn strip_ansi_sequences_removes_terminal_codes() {
    let raw = "\u{1b}[32mINFO\u{1b}[0m hello";
    assert_eq!(strip_ansi_sequences(raw), "INFO hello");
}

#[test]
fn classify_log_tags_assigns_prefix_and_severity_colors() {
    let tags = classify_log_tags("[relay] WARN pipeline failed");
    assert!(tags.contains(&"log-relay"));
    assert!(tags.contains(&"log-error") || tags.contains(&"log-warn"));
}

#[test]
#[doc = "Verifies the default console filter hides relay INFO noise."]
fn session_log_filter_hides_noisy_info_by_default_but_keeps_errors() {
    assert!(!should_show_session_log_line(
        "[relay] 2026-04-22T23:20:17Z  INFO ThreadId(01) audio packet received packet=3000",
        ConsoleLogLevel::Warn
    ));
    assert!(!should_show_session_log_line(
        "[relay] 2026-04-22T23:20:17Z  INFO ThreadId(04) decoded audio level rms=-32",
        ConsoleLogLevel::Warn
    ));
    assert!(should_show_session_log_line(
        "[relay] 2026-04-22T23:20:17Z  WARN pipeline is recovering",
        ConsoleLogLevel::Warn
    ));
    assert!(should_show_session_log_line(
        "[relay] 2026-04-22T23:20:17Z  INFO ❌ connect failed",
        ConsoleLogLevel::Error
    ));
    assert!(should_show_session_log_line(
        "[launcher] Relay connected with inputs routed to remote.",
        ConsoleLogLevel::Error
    ));
    assert!(should_show_session_log_line(
        "[relay] 2026-04-22T23:20:17Z  INFO audio packet received",
        ConsoleLogLevel::Info
    ));
}

#[test]
fn write_audio_gain_request_formats_live_control_file() {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("gain.control");
    write_audio_gain_request(&path, 425).expect("write gain");
    let raw = std::fs::read_to_string(path).expect("read gain");
    assert!(raw.starts_with("4.250 "), "{raw}");
}

#[test]
fn write_mic_gain_request_formats_live_control_file() {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("mic-gain.control");
    write_mic_gain_request(&path, 325).expect("write gain");
    let raw = std::fs::read_to_string(path).expect("read gain");
    assert!(raw.starts_with("3.250 "), "{raw}");
}

#[test]
fn write_media_control_request_formats_soft_pause_state() {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("media.control");
    let mut state = LauncherState::new();
    state.set_camera_channel_enabled(true);
    state.set_microphone_channel_enabled(false);
    state.set_audio_channel_enabled(true);
    state.select_camera(Some("Logitech BRIO".to_string()));
    state.select_camera_quality(Some(CameraMode::new(1280, 720, 30)));
    state.select_microphone(Some(
        "alsa_input.usb-Neat Microphones Bumblebee".to_string(),
    ));
    state.select_speaker(Some("bluez_output.80_C3_BA_76_26_AB.1".to_string()));

    write_media_control_request(&path, &state).expect("write media control");
    let raw = std::fs::read_to_string(&path).expect("read media control");
    assert!(raw.contains("camera=1"), "{raw}");
    assert!(raw.contains("microphone=0"), "{raw}");
    assert!(raw.contains("audio=1"), "{raw}");
    assert!(raw.contains("camera_source=b64:"), "{raw}");
    assert!(raw.contains("camera_profile=b64:"), "{raw}");
    assert!(raw.contains("microphone_source=b64:"), "{raw}");
    assert!(raw.contains("audio_sink=b64:"), "{raw}");
    assert!(raw.contains("camera_codec=mjpeg"), "{raw}");

    state.set_server_media_caps(
        Some(true),
        Some(true),
        Some("uvc".to_string()),
        Some("hevc".to_string()),
    );
    write_media_control_request(&path, &state).expect("write server-matched media control");
    let raw = std::fs::read_to_string(path).expect("read server-matched media control");
    assert!(raw.contains("camera_codec=hevc"), "{raw}");
}

#[gtk::test]
#[serial]
fn dock_all_displays_to_preview_closes_popouts_and_resets_surfaces() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-dock")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let state = Rc::new(RefCell::new(LauncherState::new()));
    state
        .borrow_mut()
        .set_display_surface(0, DisplaySurface::Window);
    state
        .borrow_mut()
        .set_display_surface(1, DisplaySurface::Window);
    let state_snapshot = state.borrow().clone();
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state_snapshot,
    );
    present_and_settle(&view.window);
    let child_proc = Rc::new(RefCell::new(None::<RelayChild>));

    let left_binding = PreviewBinding::test_stub();
    let right_binding = PreviewBinding::test_stub();
    {
        let mut popouts = view.popouts.borrow_mut();
        popouts[0] = Some(PopoutWindowHandle {
            window: gtk::ApplicationWindow::builder()
                .application(&app)
                .title("Left")
                .build(),
            root: gtk::Box::new(gtk::Orientation::Vertical, 0),
            frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
            picture: gtk::Picture::new(),
            status_label: gtk::Label::new(None),
            binding: left_binding,
        });
        popouts[1] = Some(PopoutWindowHandle {
            window: gtk::ApplicationWindow::builder()
                .application(&app)
                .title("Right")
                .build(),
            root: gtk::Box::new(gtk::Orientation::Vertical, 0),
            frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
            picture: gtk::Picture::new(),
            status_label: gtk::Label::new(None),
            binding: right_binding,
        });
    }

    dock_all_displays_to_preview(&state, &child_proc, &view.popouts, &view.widgets);

    assert!(view.popouts.borrow().iter().all(|handle| handle.is_none()));
    assert_eq!(state.borrow().display_surface(0), DisplaySurface::Preview);
    assert_eq!(state.borrow().display_surface(1), DisplaySurface::Preview);
}

#[gtk::test]
#[serial]
fn dock_all_displays_to_preview_handles_reentrant_close_callbacks() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-reentrant-dock")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let state = Rc::new(RefCell::new(LauncherState::new()));
    state
        .borrow_mut()
        .set_display_surface(0, DisplaySurface::Window);
    let state_snapshot = state.borrow().clone();
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state_snapshot,
    );
    let child_proc = Rc::new(RefCell::new(None::<RelayChild>));

    let popouts = Rc::clone(&view.popouts);
    let window = gtk::ApplicationWindow::builder()
        .application(&app)
        .title("Reentrant")
        .build();
    {
        let popouts = Rc::clone(&popouts);
        window.connect_close_request(move |_| {
            let _ = popouts.borrow_mut()[0].take();
            glib::Propagation::Proceed
        });
    }
    {
        let mut slot = popouts.borrow_mut();
        slot[0] = Some(PopoutWindowHandle {
            window,
            root: gtk::Box::new(gtk::Orientation::Vertical, 0),
            frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
            picture: gtk::Picture::new(),
            status_label: gtk::Label::new(None),
            binding: PreviewBinding::test_stub(),
        });
    }

    dock_all_displays_to_preview(&state, &child_proc, &popouts, &view.widgets);

    assert!(popouts.borrow().iter().all(|handle| handle.is_none()));
    assert_eq!(state.borrow().display_surface(0), DisplaySurface::Preview);
}

#[gtk::test]
#[serial]
fn dock_display_to_preview_restores_closed_eye_placeholder_when_relay_is_idle() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-dock-placeholder")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let state = Rc::new(RefCell::new(LauncherState::new()));
    state
        .borrow_mut()
        .set_display_surface(1, DisplaySurface::Window);
    let state_snapshot = state.borrow().clone();
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state_snapshot,
    );
    let child_proc = Rc::new(RefCell::new(None::<RelayChild>));

    let old_binding = view.widgets.display_panes[1]
        .preview_binding
        .borrow_mut()
        .take();
    if let Some(binding) = old_binding {
        binding.close();
    }
    *view.widgets.display_panes[1].preview_binding.borrow_mut() = Some(PreviewBinding::test_stub());
    view.widgets.display_panes[1]
        .preview_placeholder
        .set_visible(false);

    {
        let mut popouts = view.popouts.borrow_mut();
        popouts[1] = Some(PopoutWindowHandle {
            window: gtk::ApplicationWindow::builder()
                .application(&app)
                .title("Right")
                .build(),
            root: gtk::Box::new(gtk::Orientation::Vertical, 0),
            frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
            picture: gtk::Picture::new(),
            status_label: gtk::Label::new(None),
            binding: PreviewBinding::test_stub(),
        });
    }

    dock_display_to_preview(&state, &child_proc, &view.popouts, &view.widgets, 1);

    present_and_settle(&view.window);

    assert_eq!(state.borrow().display_surface(1), DisplaySurface::Preview);
    assert!(view.popouts.borrow()[1].is_none());
    assert!(
        view.widgets.display_panes[1].picture.paintable().is_none(),
        "idle docked pane should not retain a stale preview texture"
    );
    assert!(
        view.widgets.display_panes[1]
            .preview_placeholder
            .is_visible(),
        "closed-eye placeholder should return when docking back after disconnect"
    );
    assert_eq!(
        view.widgets.display_panes[1].stream_status.text(),
        "Connect relay to preview."
    );
}

fn realistic_device_catalog() -> DeviceCatalog {
    DeviceCatalog {
        cameras: vec!["usb-046d_Logitech_BRIO_5F6EB379-video-index0".to_string()],
        camera_modes: [(
            "usb-046d_Logitech_BRIO_5F6EB379-video-index0".to_string(),
            vec![
                CameraMode::new(1920, 1080, 30),
                CameraMode::new(1920, 1080, 20),
                CameraMode::new(1280, 720, 30),
                CameraMode::new(1280, 720, 20),
            ],
        )]
        .into_iter()
        .collect::<BTreeMap<_, _>>(),
        microphones: vec![
            "alsa_input.usb-Focusrite_Scarlett_2i2_USB_Y7ABC12345-00.analog-stereo".to_string(),
        ],
        speakers: vec![
            "alsa_output.pci-0000_00_1f.3.analog-stereo".to_string(),
            "bluez_output.80_C3_BA_76_26_AB.1".to_string(),
        ],
        keyboards: vec!["usb-Corsair_K70_RGB_PRO_Mechanical_Gaming_Keyboard-event-kbd".to_string()],
        mice: vec!["usb-Logitech_G502_X_LIGHTSPEED_Gaming_Mouse-event-mouse".to_string()],
    }
}

#[gtk::test]
#[serial]
fn shutdown_launcher_runtime_closes_preview_bindings_and_popouts() {
    if gtk::gdk::Display::default().is_none() {
        return;
    }

    let app = gtk::Application::builder()
        .application_id("dev.lesavka.test-shutdown")
        .build();
    let _ = app.register(None::<&gtk::gio::Cancellable>);

    let state = Rc::new(RefCell::new(LauncherState::new()));
    let state_snapshot = state.borrow().clone();
    let view = build_launcher_view(
        &app,
        "http://127.0.0.1:50051",
        &DeviceCatalog::default(),
        &state_snapshot,
    );
    let child_proc = Rc::new(RefCell::new(None::<RelayChild>));
    let tests = Rc::new(RefCell::new(DeviceTestController::new()));

    let left_binding = PreviewBinding::test_stub();
    let right_binding = PreviewBinding::test_stub();
    *view.widgets.display_panes[0].preview_binding.borrow_mut() = Some(left_binding.clone());
    *view.widgets.display_panes[1].preview_binding.borrow_mut() = Some(right_binding.clone());

    {
        let mut popouts = view.popouts.borrow_mut();
        popouts[0] = Some(PopoutWindowHandle {
            window: gtk::ApplicationWindow::builder()
                .application(&app)
                .title("Left")
                .build(),
            root: gtk::Box::new(gtk::Orientation::Vertical, 0),
            frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
            picture: gtk::Picture::new(),
            status_label: gtk::Label::new(None),
            binding: PreviewBinding::test_stub(),
        });
    }

    *view.diagnostics_popout.borrow_mut() = Some(
        gtk::ApplicationWindow::builder()
            .application(&app)
            .title("Diagnostics")
            .build(),
    );
    *view.log_popout.borrow_mut() = Some(
        gtk::ApplicationWindow::builder()
            .application(&app)
            .title("Log")
            .build(),
    );

    shutdown_launcher_runtime(
        &child_proc,
        &tests,
        None,
        &view.widgets,
        &view.popouts,
        &view.diagnostics_popout,
        &view.log_popout,
    );

    assert!(view.popouts.borrow().iter().all(|handle| handle.is_none()));
    assert!(
        view.widgets.display_panes[0]
            .preview_binding
            .borrow()
            .is_none()
    );
    assert!(
        view.widgets.display_panes[1]
            .preview_binding
            .borrow()
            .is_none()
    );
    assert!(view.diagnostics_popout.borrow().is_none());
    assert!(view.log_popout.borrow().is_none());
}
