//! v3.17 c2 — dogfood usecase: auth login flow (cold launch → form login → home).
//!
//! Port reference: `insight/tools/maestro/flows/auth/login.yaml`
//! & `insight/tools/maestro/subflows/launch-fresh.yaml`
//! & `insight/tools/maestro/subflows/login.yaml`. Drives a cold-launch path
//! to the logged-out landing screen, then walks the three-step form login
//! (email → org → password) and asserts the home-tab menu button is visible.
//!
//! # Why this dogfood
//!
//! Form login is the canonical credential ingress for every insight user
//! who is not session-cached. The maestro flow exercises every input
//! widget on the auth path: text field (email, org), secureTextEntry
//! field (password, with plaintext-toggle workaround), Continue / Log in
//! primary buttons, and the optional "Enter new organization" mode-switch
//! when org-select mode is the default. smix must port the same contract
//! so any future RN bundle regression on the auth surface (input ids,
//! button ids, secureTextEntry handling, org-mode default) trips this
//! example immediately. Together with `insight_dogfood_auth_landing` this
//! closes the auth surface for v3.17 c2.
//!
//! # Reversibility
//!
//! Mutating — successful run leaves the app in a logged-in state for the
//! supplied E2E credentials. `launch_fresh` ahead of the form login wipes
//! the visible session (in-app logout if a prior auth was cached) so the
//! flow always starts on the landing screen. End state is the home tab
//! with `btn-open-menu` visible, identical to maestro yaml end state.
//!
//! # Capability gap closed (G10) — v3.27 c1 ✅
//!
//! G10 (`launchApp.clearState` / `clearKeychain` orchestration) was
//! closed in v3.27 c1 (commit `0676ddf`): `App::launch_fresh(bundle_id,
//! clear_state, clear_keychain, app_path)` plus adapter env-var
//! `SMIX_APP_PATH_<NORMALIZED_BUNDLE>` (e.g.
//! `SMIX_APP_PATH_COM_FOCUSAI_APP_MOBILE=/path/to/app`) now wire the
//! host-side `uninstall + install` data-wipe path (the closest aligned
//! `simctl` semantic, since iOS has no native per-app data wipe API).
//! Without the env var the adapter walks the non-clear path
//! (`terminate + launch`) with a graceful warning — preserves v3.x
//! backward compat. The `launch_fresh` helper below (local example
//! helper, distinct from the SDK's `App::launch_fresh`) still uses the
//! visual approximation (terminate + deep-link + logout-if-needed) and
//! does NOT exercise the new SDK wipe path; for true wipe set the env
//! var and route through `Step::LaunchApp { clear_state: true }`.
//!
//! Also worth noting: maestro yaml uses `${E2E_EMAIL}` / `${E2E_ORG}` /
//! `${E2E_PASSWORD}` substitution from `.env.local`. smix examples have
//! no equivalent yaml-env substitution layer, so this dogfood reads the
//! same values from process env vars `SMIX_E2E_EMAIL` / `SMIX_E2E_ORG`
//! / `SMIX_E2E_PASSWORD` (caller must export them, sourced from
//! `~/workspace/qualcomm/insight/.env.local`). Missing any of the three
//! → hard panic with an actionable message (vs silently sending empty
//! string to the login form and failing N seconds later on a `wait_for`).
//!
//! # E2E env vars
//!
//! Required (no defaults — auth credentials are environment-scoped):
//! - `SMIX_E2E_EMAIL`    — login email (e.g. `qa+e2e@focusai.example`)
//! - `SMIX_E2E_ORG`      — org slug    (e.g. `focusai-qa`)
//! - `SMIX_E2E_PASSWORD` — login password
//!
//! Optional (inherited from other dogfood examples):
//! - `SMIX_RUNNER_PORT`  — runner http port (default 22087)
//! - `SMIX_UDID`         — sim udid binding for lifecycle ops
//! - `INSIGHT_BUNDLE_ID` — app bundle id (default `com.focusai.app.mobile`)

use smix_sdk::{App, ExpectationFailure, KeyName, SwipeDirection, TapMode, 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 auth login (v3.17 c2)");
    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_fresh: starting");
    launch_fresh(&app, &bundle_id).await?;
    println!("launch_fresh: done\n");

    match auth_login_flow(&app).await {
        Ok(()) => {
            println!("\n✓ auth login flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c2 dogfood — auth_login ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ auth login flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood auth login FAIL".into())
        }
    }
}

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

/// Approximation of maestro `subflows/launch-fresh.yaml`. G10 closed in
/// v3.27 c1: true clearState/clearKeychain parity now wired via
/// `App::launch_fresh` + `SMIX_APP_PATH_<NORM_BUNDLE>` env var. This
/// example helper still uses the visual approximation (terminate +
/// deep-link + logout-if-needed) and does not exercise the SDK wipe
/// path; set the env var and route through `Step::LaunchApp` to validate
/// the closed path on real-sim. Steps:
/// 1. terminate the app
/// 2. re-open via expo dev-client deep link
/// 3. wait for landing OR home-tab landmark (logged-in detection)
/// 4. if landed on home (btn-open-menu visible) → in-app logout to landing
async fn launch_fresh(app: &App, bundle_id: &str) -> Result<(), ExpectationFailure> {
    println!("  launch-fresh: terminate + openurl + handle logged-in (logout if needed)");
    let _ = app.terminate(bundle_id).await;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.open_url("exp+focus-ai-app://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081")
        .await?;

    // wait for either landing screen text OR a home-tab landmark.
    app.wait_for(
        &text_regex("Log in to Insight|Open menu|Device"),
        Duration::from_secs(45),
    )
    .await?;

    // logged-in detection — btn-open-menu is the canonical home-tab id.
    if app.find(&id("btn-open-menu")).await? {
        println!("  already logged in, perform logout to reach landing");

        // open the menu drawer; tolerate one missed tap (RN tab-bar jitter).
        for _ in 0..5 {
            if app
                .wait_for(&text("Dashboard"), Duration::from_millis(500))
                .await
                .is_ok()
            {
                break;
            }
            let _ = app.tap(&text("Open menu")).await;
            tokio::time::sleep(Duration::from_millis(800)).await;
        }

        // scroll the drawer up until "Log out" is in view.
        for _ in 0..10 {
            if app.find(&text("Log out")).await? {
                break;
            }
            app.scroll(&text("Log out"), SwipeDirection::Up).await?;
            tokio::time::sleep(Duration::from_millis(400)).await;
        }

        app.tap(&text("Log out")).await?;
        tokio::time::sleep(Duration::from_millis(1500)).await;
        if app.find(&text("Cancel")).await? {
            println!("  logout confirm dialog detected, tap 'Log out' to confirm");
            app.tap(&text("Log out")).await?;
        }
        app.wait_for(&text("Log in to Insight"), Duration::from_secs(30))
            .await?;
    }

    // NOTE: G10 (clearState/clearKeychain) closed in v3.27 c1 — host-side
    // wired via App::launch_fresh + SMIX_APP_PATH_<NORM_BUNDLE> env var
    // (commit 0676ddf). This helper walks the visual approximation
    // (terminate + deep-link + logout-if-needed); MMKV/Keychain residue
    // remains by design. To exercise real wipe, set the env var and
    // route through Step::LaunchApp { clear_state: true }.
    //
    // v4.1 c3 diag: wait_for above can match a brief landing flash while
    // RN render / Pressable mount is still in progress (expo dev-client
    // bundle reload race). Settle 2s before returning so the next step
    // (tap landing CTA) hits a stable Pressable instance — eliminates
    // the v4.1 c2 0/15 race signature.
    tokio::time::sleep(Duration::from_secs(2)).await;
    Ok(())
}

/// Mirror of `subflows/login.yaml` — 3-step form login.
///
/// Mapping vs yaml:
/// - `eraseText: 50`       → `App::clear` (smix-side already idempotent)
/// - `inputText: ${X}`     → `App::fill` (caller supplies value from env)
/// - `pressKey: Enter`     → `App::press_key(KeyName::Return)` (semantic
///   equivalent; smix KeyName enum spells it `Return` per Apple HW
///   keyboard key naming, not `Enter`)
/// - `tapOn: { text: X, optional: true }` → `let _ = app.tap(...).await`
///   (best-effort, ignore failure — yaml `optional: true` semantics)
/// - `hideKeyboard`        → `App::hide_keyboard`
/// - `extendedWaitUntil`   → `App::wait_for` with matching timeout
async fn login_form(app: &App) -> Result<(), ExpectationFailure> {
    let email = std::env::var("SMIX_E2E_EMAIL").expect(
        "SMIX_E2E_EMAIL env var required (set from ~/workspace/qualcomm/insight/.env.local E2E_EMAIL)",
    );
    let org = std::env::var("SMIX_E2E_ORG").expect(
        "SMIX_E2E_ORG env var required (set from ~/workspace/qualcomm/insight/.env.local E2E_ORG)",
    );
    let password = std::env::var("SMIX_E2E_PASSWORD").expect(
        "SMIX_E2E_PASSWORD env var required (set from ~/workspace/qualcomm/insight/.env.local E2E_PASSWORD)",
    );

    // G8 cap-gap closed in v4.0 c3+c4: RN Pressable `RCTTouchHandler`
    // UIGestureRecognizer now fires `onPress` reliably via the
    // `daemonProxySynthesize` mode (swift fix bypasses the XCUIElement
    // gesture recognizer chain). Single tap_with_mode replaces the
    // v3.17 retry-loop workaround.
    println!("  step 1: tap 'Log in to Insight' → email form (G8 daemonProxy)");
    app.tap_with_mode(&text("Log in to Insight"), TapMode::DaemonProxySynthesize)
        .await?;
    app.wait_for(&id("input-email"), Duration::from_secs(10))
        .await?;

    println!("  step 2: fill email");
    app.tap(&id("input-email")).await?;
    app.clear(&id("input-email")).await?;
    app.fill(&id("input-email"), &email).await?;
    app.press_key(KeyName::Return).await?;
    app.tap(&text("Continue")).await?;
    app.wait_for(
        &text_regex("Enter new organization|Organization name"),
        Duration::from_secs(10),
    )
    .await?;

    println!("  step 3: fill org (optional switch from select mode)");
    let _ = app.tap(&text("Enter new organization")).await;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.tap(&id("input-org")).await?;
    app.clear(&id("input-org")).await?;
    app.fill(&id("input-org"), &org).await?;
    app.press_key(KeyName::Return).await?;
    app.tap(&text("Continue")).await?;
    app.wait_for(&id("input-password"), Duration::from_secs(10))
        .await?;

    println!(
        "  step 4: fill password (direct fill secureTextEntry — G11 false alarm, smix fill works)"
    );
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.tap(&id("input-password")).await?;
    app.fill(&id("input-password"), &password).await?;
    app.hide_keyboard().await?;
    app.tap(&id("btn-login")).await?;
    app.wait_for(&id("btn-open-menu"), Duration::from_secs(30))
        .await?;
    Ok(())
}

// ---- auth login flow (port of flows/auth/login.yaml) --------------------

async fn auth_login_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: launch_fresh subflow already executed in main; form login next");
    login_form(app).await?;

    println!("step 2: assert home-tab btn-open-menu visible (login success)");
    app.assert_visible(&id("btn-open-menu")).await?;

    Ok(())
}
