//! v3.17 c3 — dogfood usecase: setup → AP-mode connect result (mock onboarding terminal).
//!
//! Port reference: `insight/tools/maestro/flows/setup/ap-result.yaml`
//! & subflows (`launch-warm.yaml`, `ensure-login.yaml`, `go-to-add-device.yaml`,
//! `open-menu.yaml`). Drives the full mock-SN onboarding path through the
//! AP-mode result screen — the terminal of the setup family:
//!
//! 1. launch-warm (terminate + deep-link openURL + wait landing/home)
//! 2. ensure_login (re-login if session expired)
//! 3. go_to_add_device (open drawer → tap "Add Device" → screen-add-device id)
//! 4. tap "Enter code" → tap id "input-sn" → fill "MOCK00010001" → tap "Add Device"
//! 5. extendedWaitUntil text-regex "Select Network|device not found" (30s)
//! 6. optional tap "WiFi" + "Continue"
//! 7. extendedWaitUntil text-regex "SSID|device not found" (30s)
//! 8. optional tap "Office-5G" → wait "Enter Wi-Fi Password|device not found" (30s)
//! 9. optional tap id "input-password" → fill "mockpassword" → optional tap "Connect"
//! 10. extendedWaitUntil text-regex "Wi-Fi Connected|Connecting Device|device not found" (60s)
//! 11. optional tap "Continue" to dismiss success screen
//!
//! # Why this dogfood
//!
//! Setup family terminal — `ap-result` is the deepest leg of insight's
//! provisioning flow, exercising every screen the mock-onboarding path
//! emits: SN entry → network select → WiFi list → password entry →
//! connect-result. Sister dogfoods (`setup_add_device_entry`,
//! `setup_enter_sn`, `setup_network_select`) cover the prefix legs;
//! this one validates the full chain reaches the result screen and
//! the password-fill + Connect-tap dispatch land correctly. Gates
//! three SDK capabilities composed end-to-end in a single example:
//!
//! - id-selector `fill` against the password input (`input-password`)
//! - text-selector `tap` for the "Connect" submit action
//! - long-wait (60s) `wait_for` with text-regex covering three terminal
//!   states (success / in-flight / hardware-not-found graceful branch)
//!
//! Also re-証 the open-menu repeat-until-Dashboard loop across the
//! longest dogfood path in the setup family.
//!
//! # Reversibility
//!
//! Mock SN `MOCK00010001` is recognized by insight RN bundle as a sentinel
//! that triggers the **mock onboarding flow** (no real device created,
//! no BLE/AP network roundtrip, no server provisioning state change).
//! Password string `mockpassword` against mock device is consumed
//! locally — never sent to a real AP. Server state untouched throughout.
//! Flow ends on the connect-result screen (success message / in-flight
//! spinner / "device not found" branch); next dogfood run starts with
//! `launch_warm`'s `terminate` + deep-link which forces cold-start of
//! the RN bundle, dropping all in-memory onboarding step state.
//!
//! # Capability gap surfaced
//!
//! None planned. If the iOS Local Network permission system alert
//! appears during AP-mode connect attempt (same risk class as the
//! Camera permission cap-gap on `Add Device` entry — v4 swift cycle
//! `system_popup_dismiss` API), the wait_for on the result text-regex
//! will time out and the example fails fast — cap-gap already filed.
//! Do NOT graceful-skip the terminal wait: the example's whole purpose
//! is to assert the result screen is reachable.

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 result (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_result_flow(&app).await {
        Ok(()) => {
            println!("\n✓ setup ap result flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c3 dogfood — setup_ap_result ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ setup ap result flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood setup ap result 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|Open menu"),
        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 `subflows/open-menu.yaml`: wait for `btn-open-menu`, then
/// repeat-until-Dashboard-visible pattern bounded to 5 iterations.
async fn open_menu(app: &App) -> Result<(), ExpectationFailure> {
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.wait_for(&id("btn-open-menu"), Duration::from_secs(30))
        .await?;
    for _ in 0..5 {
        if app
            .wait_for(&text("Dashboard"), Duration::from_millis(500))
            .await
            .is_ok()
        {
            return Ok(());
        }
        let _ = app.tap(&text("Open menu")).await;
        tokio::time::sleep(Duration::from_millis(800)).await;
    }
    app.wait_for(&text("Dashboard"), Duration::from_secs(5))
        .await?;
    Ok(())
}

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

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

async fn setup_ap_result_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: drawer → Add Device → screen-add-device visible");
    go_to_add_device(app).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 2: tap 'Enter code' → SN entry form revealed");
    app.tap(&text("Enter code")).await?;
    // yaml: waitForAnimationToEnd — settle the reveal animation
    tokio::time::sleep(Duration::from_millis(800)).await;

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

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

    println!("step 5: tap submit 'Add Device' (form button, distinct from drawer entry)");
    app.tap(&text("Add Device")).await?;

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

    println!("step 7: optional tap 'WiFi' (yaml optional: true — graceful no-op if absent)");
    let _ = app.tap(&text("WiFi")).await;

    println!("step 8: optional tap 'Continue' (yaml optional: true)");
    let _ = app.tap(&text("Continue")).await;

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

    println!("step 10: optional tap 'Office-5G' (yaml optional: true)");
    let _ = app.tap(&text("Office-5G")).await;

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

    println!("step 12: optional tap id 'input-password' to focus password field");
    let _ = app.tap(&id("input-password")).await;
    tokio::time::sleep(Duration::from_millis(400)).await;

    println!("step 13: fill mock password 'mockpassword' (yaml inputText, non-optional)");
    // yaml `inputText:` has no `optional:` flag → required. If
    // device-not-found branch fired, /fill against id input-password
    // will fail fast — desired behaviour: surface the divergence
    // rather than mask it.
    app.fill(&id("input-password"), "mockpassword").await?;
    tokio::time::sleep(Duration::from_millis(400)).await;

    println!("step 14: optional tap 'Connect' (yaml optional: true)");
    let _ = app.tap(&text("Connect")).await;

    println!("step 15: wait terminal 'Wi-Fi Connected|Connecting Device|device not found' (≤60s)");
    app.wait_for(
        &text_regex("Wi-Fi Connected|Connecting Device|device not found"),
        Duration::from_secs(60),
    )
    .await?;

    println!("step 16: optional tap 'Continue' to dismiss success screen (yaml optional: true)");
    let _ = app.tap(&text("Continue")).await;
    // yaml: waitForAnimationToEnd — settle the post-Continue transition
    tokio::time::sleep(Duration::from_millis(800)).await;

    Ok(())
}
