//! v5.0 C3 S1 — contract test for `scripts/v5/selftest-gate.sh`.
//!
//! The cycle-close gate is a shell script (build-fixture-app.sh pattern):
//! runs `selftest_full_surface` and validates `result.json` via `jq`, with
//! a strict env-driven interface (`SMIX_UDID` must be set). This test
//! enforces the contract structurally so the script can't silently rot:
//! file in place + executable + syntactically valid + missing-env exits
//! non-zero with a clear error.
//!
//! Run:
//!   cargo test -p smix-sdk --test selftest_gate_script

use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::Command;

fn repo_root() -> PathBuf {
    // CARGO_MANIFEST_DIR for smix-sdk = `<repo>/crates/smix-sdk`; ascend two.
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(|p| p.parent())
        .expect("smix-sdk lives under <repo>/crates")
        .to_path_buf()
}

fn gate_script_path() -> PathBuf {
    repo_root().join("scripts/v5/selftest-gate.sh")
}

#[test]
fn case1_gate_script_exists_and_executable() {
    let path = gate_script_path();
    let meta = std::fs::metadata(&path)
        .unwrap_or_else(|e| panic!("expected gate script at {path:?}: {e}"));
    assert!(meta.is_file(), "{path:?} is not a regular file");
    let mode = meta.permissions().mode();
    assert!(
        mode & 0o111 != 0,
        "{path:?} is not executable (mode = {mode:o})"
    );
}

#[test]
fn case2_gate_script_header_documents_env_interface() {
    let path = gate_script_path();
    let body = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
    // Header lives in the first ~40 lines so `head` can grep it.
    let header: String = body.lines().take(40).collect::<Vec<_>>().join("\n");
    assert!(
        header.to_lowercase().contains("usage"),
        "gate script header is missing `usage:` — interface is undocumented\n{header}"
    );
    assert!(
        header.contains("SMIX_UDID"),
        "gate script header is missing `SMIX_UDID` env reference\n{header}"
    );
}

#[test]
fn case3_gate_script_is_syntactically_valid_bash() {
    let path = gate_script_path();
    let out = Command::new("bash")
        .arg("-n")
        .arg(&path)
        .output()
        .unwrap_or_else(|e| panic!("spawn bash -n {path:?}: {e}"));
    assert!(
        out.status.success(),
        "bash -n {path:?} failed: stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
}

#[test]
fn case4_gate_exits_nonzero_when_smix_udid_missing() {
    let path = gate_script_path();
    let out = Command::new("bash")
        .arg(&path)
        .env_remove("SMIX_UDID")
        .output()
        .unwrap_or_else(|e| panic!("spawn {path:?}: {e}"));
    assert!(
        !out.status.success(),
        "gate script should exit non-zero when SMIX_UDID is unset; got success"
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("SMIX_UDID"),
        "missing-env error message should name SMIX_UDID; got stderr={stderr}"
    );
}
