use super::*;
use serial_test::serial;

fn test_args(width: usize, height: usize, fps: u32) -> Args {
    Args {
        server: DEFAULT_SERVER.to_string(),
        width,
        height,
        fps,
        duration: Duration::from_secs(1),
        jpeg_quality: DEFAULT_JPEG_QUALITY,
        session_id: 1,
        artifact_dir: None,
        print_every: 0,
        max_frame_bytes: 232_106,
        tls_ca: None,
        tls_client_cert: None,
        tls_client_key: None,
        tls_domain: None,
    }
}

#[test]
fn args_frame_timing_rounds_up_short_runs() {
    let mut args = test_args(640, 360, 30);
    args.duration = Duration::from_millis(1_050);

    assert_eq!(args.frame_step_us(), 33_333);
    assert_eq!(args.total_frames(), 32);

    args.duration = Duration::ZERO;
    assert_eq!(args.total_frames(), 1);
}

#[test]
fn encode_stats_track_bounds_mean_and_oversize_frames() {
    let mut stats = EncodeStats::default();

    assert_eq!(stats.mean_bytes(), 0);

    stats.record(100, 150);
    stats.record_sent();
    stats.record(200, 150);
    stats.record_sent();

    assert_eq!(stats.frames, 2);
    assert_eq!(stats.sent_frames, 2);
    assert_eq!(stats.min_bytes, 100);
    assert_eq!(stats.max_bytes, 200);
    assert_eq!(stats.mean_bytes(), 150);
    assert_eq!(stats.oversize_frames, 1);
}

#[test]
fn synthetic_bundle_preserves_video_audio_and_session_metadata() {
    let args = test_args(320, 180, 60);
    let bundle = synthetic_bundle(&args, 7, 116_667, vec![1, 2, 3, 4]);
    let video = bundle.video.as_ref().expect("video packet");
    let audio = bundle.audio.first().expect("audio packet");

    assert_eq!(bundle.session_id, 1);
    assert_eq!(bundle.seq, 7);
    assert_eq!(bundle.capture_start_us, 116_667);
    assert_eq!(bundle.video_width, 320);
    assert_eq!(bundle.video_height, 180);
    assert_eq!(bundle.video_fps, 60);
    assert_eq!(video.seq, 7);
    assert_eq!(video.pts, 116_667);
    assert_eq!(video.data, vec![1, 2, 3, 4]);
    assert_eq!(audio.seq, 7);
    assert_eq!(audio.sample_rate, DEFAULT_SAMPLE_RATE);
    assert_eq!(audio.channels, DEFAULT_CHANNELS);
    assert_eq!(audio.frame_duration_us, args.frame_step_us() as u32);
    assert_eq!(audio.data.len(), silence_pcm(args.frame_step_us()).len());
}

#[test]
fn synthetic_frame_helpers_handle_markers_bounds_and_silence() {
    assert_eq!(marker_cell(320, 240), 6);
    assert_eq!(marker_cell(3840, 2160), 16);
    assert_eq!(silence_pcm(0).len(), 4);
    assert_eq!(silence_pcm(20_000).len(), 3_840);

    let tiny = synthetic_rgb_frame(3, 2, 42);
    assert_eq!(tiny.len(), 18);

    let mut frame = vec![0_u8; 6 * 4 * 3];
    fill_rect(&mut frame, 6, 4, 2, 8, 8, 77);
    assert_eq!(&frame[(2 * 6 + 4) * 3..(2 * 6 + 5) * 3], &[77, 77, 77]);
    assert_eq!(&frame[(3 * 6 + 5) * 3..(3 * 6 + 6) * 3], &[77, 77, 77]);

    let first_band = synthetic_band_marker_luma(1280, 720, 1244, 4, 0x55aa);
    let second_band = synthetic_band_marker_luma(1280, 720, 1244, 68, 0x55aa);
    assert!(first_band.is_some());
    assert_ne!(first_band, second_band);
}

#[test]
fn app4_integrity_marker_carries_sequence_and_original_jpeg_crc() {
    let jpeg = [0xff, 0xd8, 0xff, 0xda, 0x00, 0x02, 0x11, 0xff, 0xd9];
    let marked = add_jpeg_app4_integrity(&jpeg, 0x0102_0304_0506_0708).expect("APP4 marker");

    assert_eq!(&marked[..4], &[0xff, 0xd8, 0xff, 0xe4]);
    assert_eq!(&marked[6..10], b"LSVK");
    assert_eq!(marked[10], 1);
    assert_eq!(&marked[11..19], &0x0102_0304_0506_0708u64.to_be_bytes());
    assert_eq!(
        &marked[19..23],
        &lesavka_server::uvc_contract::crc32_ieee(&jpeg).to_be_bytes()
    );
    assert_eq!(&marked[23..], &jpeg[2..]);
}

#[test]
fn parse_mode_and_duration_helpers_cover_valid_and_invalid_inputs() {
    assert_eq!(parse_mode("1920x1080@30").unwrap(), (1920, 1080, 30));
    assert!(parse_mode("1920x1080").is_err());
    assert!(parse_mode("1920@30").is_err());

    assert_eq!(
        duration_mul(Duration::from_millis(33), 3),
        Duration::from_millis(99)
    );
    assert_eq!(
        duration_mul(Duration::from_secs(u64::MAX), 2),
        Duration::from_nanos(u64::MAX)
    );
}

#[test]
fn uri_and_tls_helpers_identify_https_and_host_names() {
    assert!(is_https(" https://example.test:8443/path"));
    assert!(!is_https("http://example.test"));
    assert_eq!(
        host_from_uri("https://user@example.test:8443/path").as_deref(),
        Some("example.test")
    );
    assert_eq!(
        host_from_uri("https://[fd00::1]:50051").as_deref(),
        Some("fd00::1")
    );
    assert_eq!(host_from_uri("not-a-uri"), None);

    let mut args = test_args(320, 180, 30);
    args.server = "https://relay.example.test:50051".to_string();
    args.tls_ca = None;
    let err = client_tls_config(&args).expect_err("missing CA should fail");
    assert!(err.to_string().contains("--tls-ca"));
}

#[test]
#[serial]
fn env_path_and_default_pki_path_ignore_empty_values() {
    temp_env::with_vars(
        [
            ("LESAVKA_SYNTHETIC_TEST_PATH", Some("/tmp/lesavka-test")),
            ("HOME", Some("/home/tester")),
        ],
        || {
            assert_eq!(
                env_path("LESAVKA_SYNTHETIC_TEST_PATH").as_deref(),
                Some(PathBuf::from("/tmp/lesavka-test").as_path())
            );
            assert_eq!(
                default_pki_path("ca.crt").as_deref(),
                Some(PathBuf::from("/home/tester/.config/lesavka/pki/ca.crt").as_path())
            );
        },
    );

    temp_env::with_var("LESAVKA_SYNTHETIC_TEST_PATH", Some(""), || {
        assert_eq!(env_path("LESAVKA_SYNTHETIC_TEST_PATH"), None);
    });
}

#[test]
#[serial]
fn env_usize_and_summary_json_handle_empty_stats() {
    temp_env::with_var("LESAVKA_SYNTHETIC_MAX_FRAME_BYTES", Some("2048"), || {
        assert_eq!(env_usize("LESAVKA_SYNTHETIC_MAX_FRAME_BYTES"), Some(2048));
    });
    temp_env::with_var("LESAVKA_SYNTHETIC_MAX_FRAME_BYTES", Some("nope"), || {
        assert_eq!(env_usize("LESAVKA_SYNTHETIC_MAX_FRAME_BYTES"), None);
    });

    let mut args = test_args(320, 180, 30);
    args.server = "https://relay.example.test:50051".to_string();
    args.duration = Duration::from_millis(1500);
    let summary = args_summary_json(&args, None, Some("complete"));

    assert!(summary.contains("\"tls\":true"));
    assert!(summary.contains("\"encoded_frames\":0"));
    assert!(summary.contains("\"encoded_min_bytes\":null"));
    assert!(summary.contains("\"exit_reason\":\"complete\""));
}

#[test]
fn synthetic_frames_fit_safe_720p_and_1080p_isochronous_budget() {
    for (width, height, fps) in [(1280, 720, 30), (1920, 1080, 30)] {
        let args = test_args(width, height, fps);
        let mut encoder = MjpegEncoder::new(&args).expect("synthetic encoder");
        for sequence in [0, 1, 30, 120, 300] {
            let encoded = encoder.encode(sequence).expect("encode frame");
            assert!(
                encoded.len() <= args.max_frame_bytes,
                "{}x{}@{} synthetic frame {sequence} encoded to {} bytes, above {}",
                width,
                height,
                fps,
                encoded.len(),
                args.max_frame_bytes
            );
        }
    }
}
