// Contract coverage for the RCT-side UVC artifact probe.
//
// Scope: the manual long-running UVC corruption detector used against tethys.
// Targets: `scripts/manual/run_rct_uvc_artifact_probe.py`.
// Why: lower-half UVC tears are late-path artifacts, so we need a durable
// host-side probe that can catch them from the same device Google Meet reads.

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

use serde_json::Value;

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

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

#[test]
fn rct_uvc_artifact_probe_documents_late_path_lower_half_detection() {
    for expected in [
        "lesavka.rct-uvc-artifact-probe.v1",
        "Lesavka Composite",
        "lower_delta_skew",
        "lower_boundary_jump",
        "lower_flat_flash",
        "lower_slab",
        "temporal_delta_spike",
        "horizontal_shift",
        "lower_blur_flash",
        "ffmpeg",
        "v4l2",
        "x11grab",
        "--tear-threshold",
        "--shift-threshold",
        "--blur-delta-threshold",
        "--change-threshold",
        "--reference-every",
        "static_pct",
        "reference_",
        "--source",
        "--crop",
        "PGM",
        "--host",
    ] {
        assert!(
            PROBE_SRC.contains(expected),
            "RCT UVC artifact probe should preserve marker {expected}"
        );
    }
}

#[test]
fn rct_uvc_artifact_probe_self_test_flags_synthetic_lower_half_slab() {
    let dir = tempfile::tempdir().expect("tempdir");
    let output = Command::new("python3")
        .arg(repo_script_path())
        .arg("--self-test")
        .arg("--artifact-dir")
        .arg(dir.path())
        .output()
        .expect("run rct uvc artifact probe self-test");

    assert!(
        output.status.success(),
        "probe self-test should succeed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );

    let summary: Value = serde_json::from_str(
        &fs::read_to_string(dir.path().join("summary.json")).expect("summary json"),
    )
    .expect("parse summary json");
    assert_eq!(
        summary["schema"],
        "lesavka.rct-uvc-artifact-probe.self-test.v1"
    );
    assert_eq!(summary["frames"], 7);
    assert!(
        summary["suspicious_frames"].as_u64().unwrap_or_default() >= 1,
        "self-test should detect the synthetic lower-half slab: {summary}"
    );
    assert!(
        dir.path().join("reference_000001.pgm").exists(),
        "probe should save a reference frame so no-artifact runs prove the crop"
    );
    let records = summary["records"].as_array().expect("records array");
    let corrupt = records
        .iter()
        .find(|record| record["suspicious"].as_bool().unwrap_or(false))
        .expect("suspicious record");
    let reasons = corrupt["reasons"].as_array().expect("reasons array");
    assert!(
        reasons.iter().any(|reason| reason == "lower_slab")
            || reasons.iter().any(|reason| reason == "lower_flat_flash"),
        "self-test should classify the lower-half slab/flash: {corrupt}"
    );
    assert!(
        records.iter().any(|record| {
            record["reasons"]
                .as_array()
                .is_some_and(|reasons| reasons.iter().any(|reason| reason == "horizontal_shift"))
        }),
        "self-test should catch shifted frames: {summary}"
    );
    assert!(
        records.iter().any(|record| {
            record["reasons"].as_array().is_some_and(|reasons| {
                reasons
                    .iter()
                    .any(|reason| reason == "temporal_delta_spike")
            })
        }),
        "self-test should catch transient tear spikes: {summary}"
    );
    assert!(
        dir.path().join("selftest_suspicious_000005.pgm").exists(),
        "probe should save visual evidence for suspicious frames"
    );
}
