// Integration coverage for the camera runtime activation contract.
//
// Scope: validate activation error handling and configuration equality edges
// through public camera-runtime APIs.
// Targets: `server/src/camera_runtime.rs`.
// Why: camera runtime generation and guardrails are core to safe stream
// transitions, so they need direct integration-level assertions.

use lesavka_server::camera::{CameraCodec, CameraConfig, CameraOutput, HdmiConnector, HdmiMode};
use lesavka_server::camera_runtime::{CameraRuntime, camera_cfg_eq};
use serial_test::serial;
use temp_env::with_var;
use tokio::runtime::Runtime;
use tonic::Code;

#[test]
fn default_runtime_starts_without_an_active_generation() {
    let runtime = CameraRuntime::default();

    assert!(runtime.is_active(0));
    assert!(!runtime.is_active(1));
}

#[test]
fn release_if_active_supersedes_only_the_current_generation() {
    let runtime = CameraRuntime::new();
    let rt = Runtime::new().expect("runtime");

    assert!(rt.block_on(runtime.release_if_active(0)));
    assert!(!runtime.is_active(0));
    assert!(runtime.is_active(1));
    assert!(!rt.block_on(runtime.release_if_active(0)));
    assert!(runtime.is_active(1));
}

#[test]
#[serial]
fn activate_rejects_uvc_when_disabled_and_bumps_generation() {
    let runtime = CameraRuntime::new();
    let cfg = CameraConfig {
        output: CameraOutput::Uvc,
        codec: CameraCodec::Mjpeg,
        width: 1280,
        height: 720,
        fps: 25,
        hdmi: None,
    };

    with_var("LESAVKA_DISABLE_UVC", Some("1"), || {
        let rt = Runtime::new().expect("runtime");
        let result = rt.block_on(runtime.activate(&cfg));
        match result {
            Ok(_) => panic!("UVC should be disabled"),
            Err(err) => assert_eq!(err.code(), Code::FailedPrecondition),
        }
    });

    assert!(runtime.is_active(1));
    assert!(!runtime.is_active(2));
}

#[test]
#[cfg(coverage)]
fn soft_recover_supersedes_current_generation_without_hardware() {
    let runtime = CameraRuntime::new();
    let cfg = CameraConfig {
        output: CameraOutput::Hdmi,
        codec: CameraCodec::H264,
        width: 1920,
        height: 1080,
        fps: 30,
        hdmi: Some(HdmiConnector {
            name: String::from("HDMI-A-1"),
            id: Some(1),
            modes: Vec::new(),
        }),
    };
    let rt = Runtime::new().expect("runtime");

    let (session_id, _relay, _reused) = rt
        .block_on(runtime.activate(&cfg))
        .expect("coverage activation");
    assert_eq!(session_id, 1);
    assert!(runtime.is_active(1));

    rt.block_on(runtime.soft_recover());
    assert!(!runtime.is_active(1));
    assert!(runtime.is_active(2));
}

#[test]
#[serial]
fn activate_tracks_latest_generation_across_repeated_failures() {
    let runtime = CameraRuntime::new();
    let cfg = CameraConfig {
        output: CameraOutput::Uvc,
        codec: CameraCodec::Mjpeg,
        width: 640,
        height: 360,
        fps: 30,
        hdmi: None,
    };

    with_var("LESAVKA_DISABLE_UVC", Some("1"), || {
        let rt = Runtime::new().expect("runtime");
        for expected in [1_u64, 2_u64, 3_u64] {
            let result = rt.block_on(runtime.activate(&cfg));
            match result {
                Ok(_) => panic!("UVC should remain disabled"),
                Err(err) => assert_eq!(err.code(), Code::FailedPrecondition),
            }
            assert!(runtime.is_active(expected));
            assert!(!runtime.is_active(expected + 1));
        }
    });
}

#[test]
#[cfg(coverage)]
fn activate_non_uvc_returns_noop_relay_in_coverage_harness() {
    let runtime = CameraRuntime::new();
    let cfg = CameraConfig {
        output: CameraOutput::Hdmi,
        codec: CameraCodec::H264,
        width: 1920,
        height: 1080,
        fps: 30,
        hdmi: Some(HdmiConnector {
            name: String::from("HDMI-A-1"),
            id: Some(1),
            modes: Vec::new(),
        }),
    };

    let rt = Runtime::new().expect("runtime");
    let result = rt.block_on(runtime.activate(&cfg));
    let (session_id, relay, reused) = result.expect("coverage harness should create a no-op relay");
    assert_eq!(session_id, 1);
    assert!(!reused);
    relay.feed(lesavka_common::lesavka::VideoPacket {
        id: 2,
        pts: 1,
        data: vec![0, 0, 0, 1, 0x65],
        ..Default::default()
    });

    assert!(runtime.is_active(1));
    assert!(!runtime.is_active(2));
}

#[test]
fn camera_cfg_eq_handles_none_and_hdmi_connector_edges() {
    let uvc_a = CameraConfig {
        output: CameraOutput::Uvc,
        codec: CameraCodec::Mjpeg,
        width: 640,
        height: 360,
        fps: 30,
        hdmi: None,
    };
    let uvc_b = uvc_a.clone();
    assert!(camera_cfg_eq(&uvc_a, &uvc_b));

    let hdmi_a = CameraConfig {
        output: CameraOutput::Hdmi,
        codec: CameraCodec::H264,
        width: 1920,
        height: 1080,
        fps: 30,
        hdmi: Some(HdmiConnector {
            name: String::from("HDMI-A-1"),
            id: Some(7),
            modes: Vec::new(),
        }),
    };
    let hdmi_b = hdmi_a.clone();
    assert!(camera_cfg_eq(&hdmi_a, &hdmi_b));

    let mut different_id = hdmi_a.clone();
    different_id.hdmi = Some(HdmiConnector {
        name: String::from("HDMI-A-1"),
        id: Some(8),
        modes: Vec::new(),
    });
    assert!(!camera_cfg_eq(&hdmi_a, &different_id));

    let mut missing_connector = hdmi_a;
    missing_connector.hdmi = None;
    assert!(!camera_cfg_eq(&missing_connector, &hdmi_b));
}

#[test]
fn camera_cfg_eq_rejects_hdmi_display_mode_changes() {
    let base = CameraConfig {
        output: CameraOutput::Hdmi,
        codec: CameraCodec::H264,
        width: 1280,
        height: 720,
        fps: 30,
        hdmi: Some(HdmiConnector {
            name: String::from("HDMI-A-2"),
            id: Some(43),
            modes: vec![HdmiMode {
                width: 1920,
                height: 1080,
            }],
        }),
    };
    let mut changed = base.clone();
    changed.hdmi = Some(HdmiConnector {
        name: String::from("HDMI-A-2"),
        id: Some(43),
        modes: vec![HdmiMode {
            width: 1024,
            height: 768,
        }],
    });

    assert!(!camera_cfg_eq(&base, &changed));
}

#[test]
fn camera_cfg_eq_rejects_output_codec_resolution_and_fps_changes() {
    let base = CameraConfig {
        output: CameraOutput::Uvc,
        codec: CameraCodec::Mjpeg,
        width: 1280,
        height: 720,
        fps: 25,
        hdmi: None,
    };
    let mut changed = base.clone();
    changed.output = CameraOutput::Hdmi;
    assert!(!camera_cfg_eq(&base, &changed));

    changed = base.clone();
    changed.codec = CameraCodec::H264;
    assert!(!camera_cfg_eq(&base, &changed));

    changed = base.clone();
    changed.width = 1920;
    assert!(!camera_cfg_eq(&base, &changed));

    changed = base.clone();
    changed.height = 1080;
    assert!(!camera_cfg_eq(&base, &changed));

    changed = base.clone();
    changed.fps = 30;
    assert!(!camera_cfg_eq(&base, &changed));
}
