// Contract tests for UVC frame metadata artifact summarization.
//
// Scope: optional `LESAVKA_UVC_FRAME_META_LOG_PATH` artifacts.
// Targets: `scripts/manual/summarize_uvc_frame_meta_log.py`.
// Why: HEVC client-to-RCT work needs a safe local way to decide whether
// event-coded frames reached the UVC spool before we add riskier server-side
// introspection.

use std::{fs, path::PathBuf, process::Command};

use serde_json::Value;

const SUMMARIZER: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/manual/summarize_uvc_frame_meta_log.py"
));

fn repo_script_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/manual/summarize_uvc_frame_meta_log.py")
}

#[test]
fn uvc_frame_meta_summarizer_documents_boundary_diagnostics() {
    for expected in [
        "lesavka.uvc-mjpeg-spool-meta.v1",
        "lesavka.uvc-mjpeg-spool-summary.v1",
        "event_coverage",
        "source_cadence_hiccup_count",
        "decoded_pts_delta_p95_ms",
        "sequence_gap_count",
        "HEVC client-to-RCT",
    ] {
        assert!(
            SUMMARIZER.contains(expected),
            "UVC frame metadata summarizer should preserve marker {expected}"
        );
    }
}

#[test]
fn uvc_frame_meta_summarizer_reports_cadence_and_event_coverage() {
    let dir = tempfile::tempdir().expect("tempdir");
    let log_path = dir.path().join("uvc-frame-meta.jsonl");
    let timeline_path = dir.path().join("client-timeline.json");
    let json_out = dir.path().join("summary.json");
    let txt_out = dir.path().join("summary.txt");

    fs::write(
        &log_path,
        r#"not-json
{"schema":"lesavka.uvc-mjpeg-spool-meta.v1","sequence":10,"profile":"hevc-decoded-mjpeg","bytes":1000,"source_pts_us":0,"decoded_pts_us":2000,"spool_unix_ns":1000000000}
{"schema":"lesavka.uvc-mjpeg-spool-meta.v1","sequence":11,"profile":"hevc-decoded-mjpeg","bytes":1100,"source_pts_us":33333,"decoded_pts_us":35333,"spool_unix_ns":1033333000}
{"schema":"lesavka.uvc-mjpeg-spool-meta.v1","sequence":12,"profile":"hevc-decoded-mjpeg","bytes":1200,"source_pts_us":66666,"decoded_pts_us":68666,"spool_unix_ns":1066666000}
{"schema":"lesavka.uvc-mjpeg-spool-meta.v1","sequence":14,"profile":"hevc-decoded-mjpeg","bytes":1300,"source_pts_us":133333,"decoded_pts_us":135333,"spool_unix_ns":1133333000}
"#,
    )
    .expect("write log");
    fs::write(
        &timeline_path,
        r#"{"events":[{"event_id":0,"code":1,"planned_start_us":0,"planned_end_us":120000},{"event_id":1,"code":2,"planned_start_us":200000,"planned_end_us":320000}]}"#,
    )
    .expect("write timeline");

    let output = Command::new("python3")
        .arg(repo_script_path())
        .arg(&log_path)
        .arg(&json_out)
        .arg(&txt_out)
        .arg("--fps")
        .arg("30")
        .arg("--timeline")
        .arg(&timeline_path)
        .output()
        .expect("run summarizer");

    assert!(
        output.status.success(),
        "summarizer should succeed: stderr={}",
        String::from_utf8_lossy(&output.stderr)
    );

    let summary: Value =
        serde_json::from_str(&fs::read_to_string(&json_out).expect("summary json"))
            .expect("parse summary json");
    assert_eq!(summary["record_count"], 4);
    assert_eq!(summary["ignored_line_count"], 1);
    assert_eq!(summary["profiles"]["hevc-decoded-mjpeg"], 4);
    assert_eq!(summary["sequence_gap_count"], 1);
    assert_eq!(summary["source_cadence_hiccup_count"], 1);
    assert_eq!(summary["event_coverage"]["covered_events"], 1);
    assert_eq!(summary["event_coverage"]["missing_codes"][0], 2);
    assert_eq!(summary["decoded_pts_delta_median_ms"], 2.0);

    let text = fs::read_to_string(&txt_out).expect("summary text");
    assert!(text.contains("event coverage: 1/2 missing_codes=[2]"));
    assert!(text.contains("sequence: 10..14 gaps=1"));
}
