//! v5.1 c7 — `.github/workflows/selftest-gate-multi.yml` lint test。
//!
//! 跟 c5 `ci_selftest_gate_workflow.rs` 同手法,钉 multi-sim CI workflow 的
//! 关键 schema:`runs-on: macos-15`,触发条件(PR + push main + workflow_dispatch),
//! timeout ∈ [30,90] 区间(multi-sim 60 min 上限),allowed-action 白名单,
//! 必备 step 子串(包括 `selftest-gate-multi.sh` 和 `ensure-sim-smix-03`)。

use std::path::{Path, PathBuf};

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(Path::parent)
        .expect("smix-sdk lives under <repo>/crates")
        .to_path_buf()
}

fn workflow_path() -> PathBuf {
    repo_root().join(".github/workflows/selftest-gate-multi.yml")
}

fn parsed_yaml() -> serde_norway::Value {
    let body = std::fs::read_to_string(workflow_path())
        .expect("read .github/workflows/selftest-gate-multi.yml");
    serde_norway::from_str(&body).expect("yaml parses cleanly")
}

#[test]
fn case01_workflow_file_exists() {
    let path = workflow_path();
    assert!(
        path.is_file(),
        "missing workflow yaml at {path:?} — c7 hasn't landed"
    );
}

#[test]
fn case02_yaml_parses_cleanly() {
    let _ = parsed_yaml();
}

#[test]
fn case03_name_is_selftest_gate_multi() {
    let v = parsed_yaml();
    assert_eq!(
        v["name"].as_str(),
        Some("selftest-gate-multi"),
        "name field must be 'selftest-gate-multi'",
    );
}

#[test]
fn case04_pull_request_branches_contain_main_and_develop() {
    let v = parsed_yaml();
    let branches = v["on"]["pull_request"]["branches"]
        .as_sequence()
        .expect("on.pull_request.branches is a sequence");
    let names: Vec<&str> = branches.iter().filter_map(|b| b.as_str()).collect();
    assert!(
        names.contains(&"main") && names.contains(&"develop"),
        "pull_request branches must contain main + develop, got {names:?}",
    );
}

#[test]
fn case05_push_branches_contain_main() {
    let v = parsed_yaml();
    let branches = v["on"]["push"]["branches"]
        .as_sequence()
        .expect("on.push.branches is a sequence");
    let names: Vec<&str> = branches.iter().filter_map(|b| b.as_str()).collect();
    assert!(
        names.contains(&"main"),
        "push branches must contain main, got {names:?}"
    );
}

#[test]
fn case06_on_contains_workflow_dispatch() {
    let v = parsed_yaml();
    assert!(
        v["on"]
            .as_mapping()
            .map(|m| m.contains_key("workflow_dispatch"))
            .unwrap_or(false),
        "on must contain workflow_dispatch key",
    );
}

#[test]
fn case07_runs_on_is_macos_15() {
    let v = parsed_yaml();
    assert_eq!(
        v["jobs"]["selftest-gate-multi"]["runs-on"].as_str(),
        Some("macos-15"),
        "selftest-gate-multi must run on macos-15",
    );
}

#[test]
fn case08_timeout_within_30_to_90() {
    let v = parsed_yaml();
    let t = v["jobs"]["selftest-gate-multi"]["timeout-minutes"]
        .as_u64()
        .expect("timeout-minutes is a positive int");
    assert!(
        (30..=90).contains(&t),
        "timeout must be in [30, 90] for multi-sim;got {t}",
    );
}

#[test]
fn case09_yaml_text_mentions_required_subjects() {
    let body = std::fs::read_to_string(workflow_path()).expect("read workflow yaml");
    for needle in [
        "actions/checkout",
        "selftest-gate-multi.sh",
        "sim-smix-02",
        "sim-smix-03",
        "ensure-sim-smix-03",
        "smix-server",
        "summary.json",
        "selftest-multi",
        "SMIX_UDIDS",
    ] {
        assert!(
            body.contains(needle),
            "yaml body must mention `{needle}` — c7 gate-multi / 2-sim alias / artifact 必备",
        );
    }
}

#[test]
fn case10_uses_only_allowed_actions() {
    let body = std::fs::read_to_string(workflow_path()).expect("read workflow yaml");
    let allowed = [
        "actions/checkout@v4",
        "actions/cache@v4",
        "actions/upload-artifact@v4",
        "dtolnay/rust-toolchain@stable",
    ];
    for line in body.lines() {
        let trimmed = line.trim();
        let action = if let Some(a) = trimmed.strip_prefix("- uses:") {
            Some(a.trim())
        } else {
            trimmed.strip_prefix("uses:").map(str::trim)
        };
        if let Some(action) = action {
            assert!(
                allowed.contains(&action),
                "third-party action {action:?} not in allow list; allowed: {allowed:?}",
            );
        }
    }
}

#[test]
fn case11_uploads_selftest_multi_artifact() {
    let body = std::fs::read_to_string(workflow_path()).expect("read workflow yaml");
    assert!(
        body.contains("actions/upload-artifact@v4"),
        "must upload-artifact for selftest-multi result",
    );
    assert!(
        body.contains(".smix/selftest-multi"),
        "must upload .smix/selftest-multi/ path",
    );
}
