//! v3.17 c3 — dogfood usecase: setup → AP-mode Wi-Fi password entry.
//!
//! Port reference: `insight/tools/maestro/flows/setup/ap-wifi-password.yaml`
//! & `insight/tools/maestro/subflows/{launch-warm,ensure-login,go-to-add-device}.yaml`.
//! Drives the add-device entry → mock SN → AP-mode network selection →
//! pick a mock SSID → land on "Enter Wi-Fi Password" → focus the
//! `input-password` field → type a mock password → assert the `Connect`
//! button is visible.
//!
//! # Why this dogfood
//!
//! v3.17 c3 setup-series cross-validates the device-setup wizard funnel
//! end-to-end. Prior setup legs (add-device-entry / enter-sn / network-select
//! / ap-wifi-list) only drive id/text taps and text inputs into plain
//! `<TextInput>`. This leg is the first to drive a **secureTextEntry**
//! input — the password field on the Enter Wi-Fi Password screen renders
//! as a masked input. If RN's `<TextInput secureTextEntry>` does not
//! accept the standard typeText path that /fill goes through, this is
//! the surface where the cap-gap appears (see "Capability gap surfaced"
//! below).
//!
//! Also exercises:
//!
//! - launch_warm subflow port (stopApp → openLink deep-link → wait
//!   landing|home) shared with v3.17 c1/c2
//! - long-funnel optional-tap chain (WiFi/Continue/SSID/input-password
//!   each `optional: true` in the yaml — gracefully no-op when the mock
//!   harness auto-advances past a screen)
//! - terminal `assertVisible` is `optional: true` in the yaml; we keep
//!   it as a best-effort wait_for to confirm the Connect button surfaces
//!
//! # Reversibility
//!
//! Pure read against the mock device backend — the typed SN `MOCK00010001`
//! and password `mockpassword` are never persisted to a real device; the
//! mock simulates the entire AP-mode handshake locally. No network
//! credentials are committed; tapping `Connect` (which this example does
//! NOT do) would only drive the mock state machine forward. Backing out
//! through `btn-back` (not driven here) restores the previous wizard
//! screen.
//!
//! # Capability gap surfaced
//!
//! Possible G11 candidate — RN `<TextInput secureTextEntry>` may reject
//! the typeText path /fill currently dispatches through. If app.fill
//! returns Err on the masked input, we surface that explicitly in the
//! step log; the example still graceful-skips the assertVisible terminal
//! check so the e2e doesn't hard-fail when the cap-gap manifests. A real
//! G11 cap-gap entry would then be opened to add a secureTextEntry-aware
//! fill dispatch path.
//!
//! **v3.27 c2 probe outcome**: blocked — upstream step 6 (`Select Network
//! |device not found` ≤30s after MOCK00010001 Add Device handshake) timed
//! out because the insight mock backend's AP-mode response was not wired
//! (G12 mock onboarding dependency, external to smix core). flow never
//! reached step 13 secureTextEntry fill, so G11 capability could not be
//! confirm-or-closed. v3.27 c2 leaves G11 entry as candidate in
//! `docs/v4-backlog.md`; v4 swift cycle should first unblock the probe by
//! wiring G12 mock infra, then re-probe to decide confirm-or-close.
//! Probe log archived at `docs/plan-history/v3.27-c2-probe.txt`.

use smix_sdk::{App, ExpectationFailure, id, text, text_regex};
use std::time::Duration;

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let port: u16 = std::env::var("SMIX_RUNNER_PORT")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(22087);
    let udid = std::env::var("SMIX_UDID").ok();

    println!("smix dogfood — insight setup ap-wifi-password (v3.17 c3)");
    println!("========================================================");
    println!("runner port: {port}");
    println!("udid:        {:?}", udid);
    println!();

    let mut app = App::connect_to_runner(port).await?;
    if let Some(u) = udid {
        app = app.with_udid(u);
    }

    let bundle_id =
        std::env::var("INSIGHT_BUNDLE_ID").unwrap_or_else(|_| "com.focusai.app.mobile".to_string());

    println!("launch_warm: starting");
    launch_warm(&app, &bundle_id).await?;
    println!("launch_warm: done\n");

    println!("ensure_login: starting");
    ensure_login(&app).await?;
    println!("ensure_login: done\n");

    println!("reset_to_home");
    reset_to_home(&app).await?;
    println!("reset_to_home: done\n");

    println!("close_dev_menu_if_open");
    close_dev_menu_if_open(&app).await?;
    println!("close_dev_menu_if_open: done\n");

    match setup_ap_wifi_password_flow(&app).await {
        Ok(()) => {
            println!("\n✓ setup ap-wifi-password flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c3 dogfood — setup_ap_wifi_password ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ setup ap-wifi-password flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood setup ap-wifi-password FAIL".into())
        }
    }
}

// ---- inline helpers (each example self-contained) -----------------------

/// Mirror of `subflows/launch-warm.yaml`: stopApp → openLink deep-link
/// (passes metro URL as launch argument) → wait landing/home (≤45s) →
/// settle. Used to cold-warm the app into a known baseline state.
async fn launch_warm(app: &App, bundle_id: &str) -> Result<(), ExpectationFailure> {
    println!("  terminating {bundle_id}");
    let _ = app.terminate(bundle_id).await;
    tokio::time::sleep(Duration::from_millis(800)).await;

    let deep_link = "exp+focus-ai-app://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081";
    println!("  open_url deep-link → metro");
    app.open_url(deep_link).await?;

    println!("  waiting for landing or home (≤45s)");
    app.wait_for(
        &text_regex("Log in to Insight|Device"),
        Duration::from_secs(45),
    )
    .await?;
    tokio::time::sleep(Duration::from_millis(800)).await;
    Ok(())
}

async fn ensure_login(app: &App) -> Result<(), ExpectationFailure> {
    // v3.14 c2 robust pattern: double-check login_text + dev_bubble both
    // visible. `find` (Apple element query) can flash login text for a
    // single tick during RN home-screen mount even when session cached.
    // Insist on dev-bubble; if gone, already authenticated.
    let login_text_visible = app.find(&text("Log in to Insight")).await?;
    let dev_bubble_visible = app.find(&id("dev-bubble")).await?;
    if !(login_text_visible && dev_bubble_visible) {
        println!("  already logged in (login screen not stable)");
        return Ok(());
    }
    println!("  tapping dev-bubble");
    app.tap(&id("dev-bubble")).await?;
    tokio::time::sleep(Duration::from_millis(1500)).await;
    println!("  tapping 'Login'");
    app.tap(&text("Login")).await?;
    println!("  waiting for home tab");
    app.wait_for(
        &text_regex("Open menu|Device|Search|Merlin|Alerts|Tracking"),
        Duration::from_secs(30),
    )
    .await?;
    Ok(())
}

async fn reset_to_home(app: &App) -> Result<(), ExpectationFailure> {
    for _ in 0..3 {
        let drawer_open = app
            .wait_for(&text("Dashboard"), Duration::from_millis(200))
            .await
            .is_ok();
        if !drawer_open {
            break;
        }
        let _ = app.go_back().await;
        tokio::time::sleep(Duration::from_millis(400)).await;
    }
    if app.find(&text("Device")).await? {
        let _ = app.tap(&text("Device")).await;
        tokio::time::sleep(Duration::from_millis(800)).await;
    }
    // v3.14 c2 robust fallback: any bottom-tab label (insight RN bundle
    // dropped `Open menu` label on some screens; Merlin tab header lacks it).
    app.wait_for(
        &text_regex("Open menu|Device|Search|Merlin|Alerts|Tracking"),
        Duration::from_secs(5),
    )
    .await?;
    Ok(())
}

async fn close_dev_menu_if_open(app: &App) -> Result<(), ExpectationFailure> {
    let open = app
        .wait_for(&text("Reboot"), Duration::from_millis(300))
        .await
        .is_ok();
    if open {
        println!("  dev menu visible, tapping dev-bubble");
        app.tap(&id("dev-bubble")).await?;
        tokio::time::sleep(Duration::from_millis(800)).await;
    } else {
        println!("  dev menu already dismissed");
    }
    Ok(())
}

/// Mirror of v3.4 c4 3-retry drawer-open pattern. Tap "Open menu" until
/// the in-drawer "Dashboard" label is visible (proves drawer mounted).
async fn open_menu(app: &App) -> Result<(), ExpectationFailure> {
    for attempt in 0..3 {
        if app
            .wait_for(&text("Dashboard"), Duration::from_millis(200))
            .await
            .is_ok()
        {
            return Ok(());
        }
        if attempt == 0 {
            app.tap(&text("Open menu")).await?;
        } else {
            let _ = app.tap(&text("Open menu")).await;
        }
        if app
            .wait_for(&text("Dashboard"), Duration::from_secs(3))
            .await
            .is_ok()
        {
            return Ok(());
        }
    }
    app.wait_for(&text("Dashboard"), Duration::from_secs(5))
        .await?;
    Ok(())
}

/// Mirror of `subflows/go-to-add-device.yaml`: open the drawer → tap
/// "Add Device" → wait for `screen-add-device` to mount (≤30s).
async fn go_to_add_device(app: &App) -> Result<(), ExpectationFailure> {
    println!("  open_menu");
    open_menu(app).await?;
    println!("  tap 'Add Device' drawer entry");
    app.tap(&text("Add Device")).await?;
    println!("  wait for screen-add-device (≤30s)");
    app.wait_for(&id("screen-add-device"), Duration::from_secs(30))
        .await?;
    Ok(())
}

// ---- setup ap-wifi-password flow (port of flows/setup/ap-wifi-password.yaml) ----

async fn setup_ap_wifi_password_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: navigate drawer → Add Device → screen-add-device");
    go_to_add_device(app).await?;

    println!("step 2: tap 'Enter code' to open the SN entry sheet");
    app.tap(&text("Enter code")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 3: tap input-sn to focus the SN field");
    app.tap(&id("input-sn")).await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 4: fill mock SN 'MOCK00010001'");
    app.fill(&id("input-sn"), "MOCK00010001").await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 5: tap 'Add Device' to start the AP-mode handshake");
    app.tap(&text("Add Device")).await?;

    println!("step 6: wait Select Network or device-not-found (≤30s)");
    app.wait_for(
        &text_regex("Select Network|device not found"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 7: tap 'WiFi' (optional — mock may auto-advance)");
    let _ = app.tap(&text("WiFi")).await;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 8: tap 'Continue' (optional — mock may auto-advance)");
    let _ = app.tap(&text("Continue")).await;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 9: wait SSID list header or device-not-found (≤30s)");
    app.wait_for(
        &text_regex("SSID|device not found"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 10: tap 'Office-5G' mock SSID (optional — graceful no-op if absent)");
    let _ = app.tap(&text("Office-5G")).await;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 11: wait 'Enter Wi-Fi Password' header or device-not-found (≤30s)");
    app.wait_for(
        &text_regex("Enter Wi-Fi Password|device not found"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 12: tap input-password (optional — graceful no-op if absent)");
    let _ = app.tap(&id("input-password")).await;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 13: fill 'mockpassword' into secureTextEntry input");
    match app.fill(&id("input-password"), "mockpassword").await {
        Ok(()) => {
            println!("  fill OK — secureTextEntry accepted typeText path");
            tokio::time::sleep(Duration::from_millis(500)).await;
        }
        Err(e) => {
            // Potential G11 cap-gap: RN <TextInput secureTextEntry> may
            // refuse the standard typeText dispatch. Surface explicitly
            // and continue (yaml's terminal assertVisible is optional).
            // v3.27 c2 probe blocked by upstream step 6 (G12 mock infra
            // dependency); flow did not reach this branch in that run.
            // G11 confirm-or-close deferred to v4 swift cycle after G12
            // mock infra is wired upstream.
            println!("  fill FAILED on secureTextEntry — likely G11 cap-gap");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
        }
    }

    println!("step 14: assert Connect button visible (optional in yaml — best-effort)");
    match app.wait_for(&text("Connect"), Duration::from_secs(5)).await {
        Ok(_) => println!("  Connect visible"),
        Err(_) => println!("  Connect not visible (yaml marks optional)"),
    }

    Ok(())
}
