//! v5.0 C1 — tests for the selftest capability-cover verifier.
//!
//! Four cases:
//!  1. Mock `lib.rs` containing an unclassified `pub fn` → verifier
//!     returns `Unclassified` listing the offending name.
//!  2. Real `crates/smix-sdk/src/lib.rs` against allowlists that
//!     deliberately omit `tap_at_coord` from both C1 and C2 → verifier
//!     catches it as Unclassified. This is the red TDD step that
//!     proves the verifier detects the kind of allowlist drift the
//!     v5.0 retrogression gate exists to surface.
//!  3. Same lib.rs against the **real** allowlists → verifier returns
//!     `Ok` with coverage ≥ 41%.
//!  4. Allowlists that duplicate `tap_at_coord` in both C1 and C2 →
//!     verifier returns `Duplicate { name: "tap_at_coord", … }`.

use smix_sdk::selftest::verifier::{
    C1_COVER, C2_DEFERRED, PERMANENT_SKIP, VerifyError, VerifyReport, verify_cover,
};

const REAL_LIB_RS: &str = include_str!("../src/lib.rs");

#[test]
fn case1_mock_lib_with_unclassified_pub_fn() {
    let mock = "pub fn known_thing() {}\npub fn unclassified_new_method() {}\n";
    let c1: &[&str] = &["known_thing"];
    let c2: &[&str] = &[];
    let skip: &[&str] = &[];
    match verify_cover(mock, c1, c2, skip) {
        Err(VerifyError::Unclassified(names)) => {
            assert!(
                names.iter().any(|n| n == "unclassified_new_method"),
                "expected verifier to flag `unclassified_new_method`, got {names:?}"
            );
            assert!(
                !names.iter().any(|n| n == "known_thing"),
                "known_thing was in C1; should not be flagged"
            );
        }
        other => panic!("expected Err(Unclassified), got {other:?}"),
    }
}

#[test]
fn case2_real_lib_rs_with_c1_and_c2_missing_tap_at_coord() {
    // Drop `tap_at_coord` from both C1 and C2 — leave PERMANENT_SKIP
    // and the rest of the lists alone. Verifier must catch it.
    let c1_missing: Vec<&str> = C1_COVER
        .iter()
        .copied()
        .filter(|n| *n != "tap_at_coord")
        .collect();
    let c2_missing: Vec<&str> = C2_DEFERRED
        .iter()
        .copied()
        .filter(|n| *n != "tap_at_coord")
        .collect();
    match verify_cover(REAL_LIB_RS, &c1_missing, &c2_missing, PERMANENT_SKIP) {
        Err(VerifyError::Unclassified(names)) => {
            assert!(
                names.iter().any(|n| n == "tap_at_coord"),
                "expected verifier to flag `tap_at_coord`, got {names:?}"
            );
        }
        other => panic!("expected Err(Unclassified) with tap_at_coord, got {other:?}"),
    }
}

#[test]
fn case3_real_lib_rs_with_real_allowlists_is_ok_and_coverage_above_41pct() {
    let report = match verify_cover(REAL_LIB_RS, C1_COVER, C2_DEFERRED, PERMANENT_SKIP) {
        Ok(r) => r,
        Err(e) => panic!("expected verifier Ok against real allowlists, got {e:?}"),
    };
    assert!(
        report.sdk_pub_fn_count >= 30,
        "expected >= 30 pub fn in real lib.rs, got {}",
        report.sdk_pub_fn_count,
    );
    assert!(
        report.classified_count == report.sdk_pub_fn_count,
        "every pub fn must be classified when Ok; classified={} pub_fn={}",
        report.classified_count,
        report.sdk_pub_fn_count,
    );
    assert!(
        report.coverage_pct >= 39.0,
        "expected coverage_pct >= 39.0 (17 of 43 ≈ 39.5%), got {}",
        report.coverage_pct,
    );
}

#[test]
fn case4_duplicate_name_across_lists_is_caught() {
    // Force-insert `connect_to_runner` into C1 while leaving the
    // real PERMANENT_SKIP entry in place — both lists now claim it,
    // which is the curation-bug shape the verifier exists to surface.
    let mut c1_dup: Vec<&str> = C1_COVER.to_vec();
    c1_dup.push("connect_to_runner");
    match verify_cover(REAL_LIB_RS, &c1_dup, C2_DEFERRED, PERMANENT_SKIP) {
        Err(VerifyError::Duplicate {
            name,
            first_list,
            second_list,
        }) => {
            assert_eq!(name, "connect_to_runner");
            // `first_list` is whichever list was scanned first
            // (C1_COVER, by the iteration order in `verify_cover`).
            assert_eq!(first_list, "C1_COVER");
            assert_eq!(second_list, "PERMANENT_SKIP");
        }
        other => panic!("expected Err(Duplicate), got {other:?}"),
    }
}

// --- v5.0 C2 — coverage_pct denominator correction ----------------------
//
// PERMANENT_SKIP entries are builders / getters / setup-time accessors
// (`new`, `connect_to_runner`, `with_udid`, `udid`, `driver`, `simctl`) —
// not capabilities. Counting them in the denominator of `coverage_pct`
// punishes the score for SDK surface that was never a capability target.
// The correct denominator is `sdk_pub_fn_count - permanent_skip_count`
// (capability denominator). The two cases below pin that contract.

#[test]
fn case5_coverage_pct_excludes_permanent_skip_from_denominator() {
    // v5.1 c1 shape: 43 pub fn / 37 in C1 (focused live-wired) / 0 deferred / 6 skipped.
    // Capability denominator = 43 - 6 = 37 ⇒ 37/37 = 100.0%.
    let r = VerifyReport::from_counts(43, 37, 0, 6);
    assert!(
        (r.coverage_pct - 100.0).abs() < 0.01,
        "coverage_pct expected 100.0 (capability denominator after v5.1 c1), got {}",
        r.coverage_pct,
    );
}

#[test]
fn case6_coverage_pct_hits_100_when_no_capability_outstanding() {
    // Hypothetical future shape: all non-skipped pub fn covered.
    // 40 - 4 = 36 capability surface, 36 in C1 ⇒ 100.0%.
    let r = VerifyReport::from_counts(40, 36, 0, 4);
    assert!(
        (r.coverage_pct - 100.0).abs() < 0.01,
        "coverage_pct expected 100.0 when capability denominator == c1, got {}",
        r.coverage_pct,
    );
}
