//! v5.0 C1 — full-SDK-surface selftest example.
//!
//! Drives one smix-owned booted sim through the C1 capability matrix
//! (17 segments: 8 sense + 9 act) against the insight RN app, then
//! dumps `result.json` + `report.md` to `.smix/selftest/<ts>/`. Process
//! exit code is 0 iff every recorded capability is `Passed`.
//!
//! Intended as the **retrogression gate** entry point — later cycles
//! run this example as part of cycle-close and refuse to close on a
//! non-zero exit. The cycle-close gate hook is the C3 deliverable; for
//! C1 we just need the example to run clean on real-sim.
//!
//! ## Usage
//!
//! ```bash
//! cargo run --release --example selftest_full_surface -- <UDID>
//! ```
//!
//! Env vars (optional but recommended — without all three the login
//! form capability segments record `Skipped`):
//! - `SMIX_E2E_EMAIL`
//! - `SMIX_E2E_ORG`
//! - `SMIX_E2E_PASSWORD`
//! - `INSIGHT_BUNDLE_ID` (default `com.focusai.app.mobile`)
//! - `SMIX_RUNNER_PORT`  (default 22087)

use std::path::PathBuf;
use std::process::ExitCode;

use smix_sdk::App;
use smix_sdk::selftest::{Harness, scenario, verifier};

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() -> ExitCode {
    // === verify the cover allowlist matches the SDK surface FIRST ===
    // Bail with a panic message that names every unclassified pub fn
    // before any sim work happens — a stale allowlist is a developer
    // bug, not a runtime fixture failure.
    let lib_rs = include_str!("../src/lib.rs");
    let verify_report = verifier::verify_cover_or_panic(lib_rs);
    eprintln!(
        "selftest: SDK surface ok — {} pub fn classified (C1 {} / C2 {} / skip {}); coverage {:.1}%",
        verify_report.sdk_pub_fn_count,
        verify_report.c1_cover_count,
        verify_report.c2_deferred_count,
        verify_report.permanent_skip_count,
        verify_report.coverage_pct,
    );

    // === parse UDID from argv ===
    let udid = match std::env::args().nth(1) {
        Some(u) => u,
        None => {
            eprintln!(
                "usage: selftest_full_surface <UDID>\n\
                 (pass a smix-owned sim UDID — never sim-insight.)"
            );
            return ExitCode::from(64);
        }
    };

    // === connect runner + bind UDID ===
    let port: u16 = std::env::var("SMIX_RUNNER_PORT")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(22087);
    let app = match App::connect_to_runner(port).await {
        Ok(a) => a.with_udid(&udid),
        Err(e) => {
            eprintln!("selftest: connect_to_runner({port}) failed: {}", e.message);
            return ExitCode::from(1);
        }
    };

    // === harness — creates .smix/selftest/<ts>/ ===
    let parent_dir = PathBuf::from(".smix/selftest");
    let mut harness = match Harness::new(&parent_dir, udid.clone()) {
        Ok(h) => h,
        Err(e) => {
            eprintln!("selftest: failed to create run dir: {e}");
            return ExitCode::from(1);
        }
    };

    // === scenario dispatcher — full C1 capability matrix ===
    let env = scenario::ScenarioEnv::from_process_env();
    // Camera permission back to "not determined" BEFORE the fixture
    // launches (simctl privacy may terminate a running target app), so
    // the system_popup_action segment gets a deterministic permission
    // alert instead of a silent already-granted no-op.
    if let Err(e) = app
        .simctl()
        .reset_permission(&udid, smix_sdk::SimctlPermission::Camera, &env.bundle_id)
        .await
    {
        eprintln!("selftest: camera permission reset failed (non-fatal): {e}");
    }
    eprintln!(
        "selftest: starting C1 matrix on {udid} (bundle={}, app_path={})",
        env.bundle_id,
        env.app_path.as_deref().unwrap_or("<none>")
    );

    // v5.1 c4 — 硬胶囊化:scenario 跑前 start_capsule_recording,跑后
    // stop_capsule_recording_and_reconcile,把 summary 钉进 result.json
    // 顶层 `capsule_reconcile`。runner 必须用 `TEST_RUNNER_SMIX_RECORD_ENABLED=1`
    // 起(走 `smix capsule up`,不走裸 `smix runner up`)。失败时仍跑
    // scenario(不阻断),只是不填 capsule_reconcile,gate 会按 `null`
    // 处理为 exit 11(unattributed_count != 0 同源条件)。
    let capsule_started = match app.start_capsule_recording().await {
        Ok(()) => true,
        Err(e) => {
            eprintln!(
                "selftest: start_capsule_recording failed: {} \
                 — 跑 `smix capsule up <UDID>` 走硬胶囊化(带 \
                 TEST_RUNNER_SMIX_RECORD_ENABLED=1),不要裸 `smix runner up`。\
                 scenario 仍会跑,但 result.json `capsule_reconcile` 留空,\
                 gate 会按 exit 11 处理。",
                e.message
            );
            false
        }
    };

    scenario::run_c1_segments(&app, &mut harness, &env).await;

    if capsule_started {
        match app.stop_capsule_recording_and_reconcile(None).await {
            Ok(recon) => {
                let summary =
                    smix_sdk::selftest::CapsuleReconcileSummary::from_reconciliation(&recon);
                eprintln!(
                    "selftest: capsule_reconcile = {{ issued: {}, focus_change: {}, attributed: {}, unattributed: {} }}",
                    summary.issued_count,
                    summary.focus_change_count,
                    summary.attributed_count,
                    summary.unattributed_count
                );
                harness.set_capsule_reconcile(summary);
            }
            Err(e) => {
                eprintln!(
                    "selftest: stop_capsule_recording_and_reconcile failed: {} \
                     — result.json `capsule_reconcile` 留空,gate 会按 exit 11 处理。",
                    e.message
                );
            }
        }
    }

    // === dump result.json + report.md + symlink ===
    if let Err(e) = harness.write_result_json(&verify_report) {
        eprintln!("selftest: write_result_json failed: {e}");
    }
    if let Err(e) = harness.write_report_md(&verify_report) {
        eprintln!("selftest: write_report_md failed: {e}");
    }
    if let Err(e) = harness.update_latest_symlink() {
        eprintln!("selftest: update_latest_symlink failed: {e}");
    }

    // === summary print ===
    let count =
        |statuses: &std::collections::BTreeMap<String, smix_sdk::selftest::CapabilityStatus>| {
            let (mut p, mut f, mut s) = (0u32, 0u32, 0u32);
            for status in statuses.values() {
                match status {
                    smix_sdk::selftest::CapabilityStatus::Passed { .. } => p += 1,
                    smix_sdk::selftest::CapabilityStatus::Failed { .. } => f += 1,
                    smix_sdk::selftest::CapabilityStatus::Skipped { .. } => s += 1,
                }
            }
            (p, f, s)
        };
    let (cap_p, cap_f, cap_s) = count(harness.capability_status());
    let (spec_p, spec_f, spec_s) = count(harness.selector_spec_status());
    eprintln!(
        "selftest: matrix done — capability {cap_p}P/{cap_f}F/{cap_s}S + selector_spec {spec_p}P/{spec_f}F/{spec_s}S \
         (run dir {})",
        harness.run_dir().display()
    );

    ExitCode::from(harness.finish() as u8)
}
