//! v5.1 c3 S3 — real-sim 集成测,验软胶囊 G angle 端到端。
//!
//! 前置(`#[ignore]` 手跑前):
//!   target/release/smix sim boot 02
//!   bash scripts/v5/build-fixture-app.sh <UDID>
//!   TEST_RUNNER_SMIX_RECORD_ENABLED=1 \
//!     target/release/smix runner up 02 --bundle dev.smix.SelftestFixture
//!
//! 跑:
//!   cargo test -p smix-sdk --test capsule_reconciliation -- --ignored --nocapture

use std::time::Duration;

use smix_sdk::{App, id};

fn first_smix_owned_booted_udid() -> Option<String> {
    let out = std::process::Command::new("xcrun")
        .args(["simctl", "list", "devices", "booted"])
        .output()
        .ok()?;
    let text = String::from_utf8_lossy(&out.stdout);
    for line in text.lines() {
        let trimmed = line.trim_start();
        if !trimmed.starts_with("sim-smix-") {
            continue;
        }
        let open = line.find('(')?;
        let close = line[open + 1..].find(')')?;
        let udid = &line[open + 1..open + 1 + close];
        if udid.len() == 36 {
            return Some(udid.to_string());
        }
    }
    None
}

#[ignore = "real-sim — manual run via --ignored; needs sim-smix-02 + SelftestFixture + runner up TEST_RUNNER_SMIX_RECORD_ENABLED=1"]
#[tokio::test]
async fn capsule_records_attribute_sdk_fill_focus_change() {
    let Some(udid) = first_smix_owned_booted_udid() else {
        eprintln!("no smix-owned booted sim — skipping capsule integration test");
        return;
    };
    let app = App::connect_to_runner(22087)
        .await
        .expect("connect_to_runner 22087")
        .with_udid(&udid);

    let _ = app.terminate("dev.smix.SelftestFixture").await;
    app.launch("dev.smix.SelftestFixture")
        .await
        .expect("launch fixture");
    tokio::time::sleep(Duration::from_millis(1500)).await;

    app.start_capsule_recording()
        .await
        .expect("start_capsule_recording");

    // SDK 自发的 fill — issued ledger 应该收一条 Fill,EventRecorder 应该
    // 收一条 1018(input-email 获焦)。
    app.fill(&id("input-email"), "qa@x.com")
        .await
        .expect("fill input-email");
    tokio::time::sleep(Duration::from_millis(500)).await;

    let recon = app
        .stop_capsule_recording_and_reconcile(None)
        .await
        .expect("stop_capsule_recording_and_reconcile");

    eprintln!("capsule reconciliation: {recon:?}");
    // diagnostic — issued vs event timestamps to size window
    if let Some(evt) = recon.unattributed_events.first() {
        eprintln!(
            "diag: first unattributed event ts = {} / diag log via /record/poll buffer",
            evt.timestamp_ms
        );
    }

    assert!(
        recon.issued_count >= 1,
        "至少 fill 落一条 issued, got {}",
        recon.issued_count
    );
    assert!(
        recon.focus_change_count >= 1,
        "fill 必触 input-email 焦点变化 (1018), got {}",
        recon.focus_change_count
    );
    assert!(
        recon.attributed_count >= 1,
        "fill 触的 1018 必落在 ± 500ms 时间窗里 → 至少 1 个 attributed, got {}",
        recon.attributed_count
    );
    assert_eq!(
        recon.unattributed_count, 0,
        "整段无外来 user 干预 → unattributed 必 0, got {} / events: {:#?}",
        recon.unattributed_count, recon.unattributed_events
    );
}
