//! v3.17 c2 — dogfood usecase: auth logout (login → logout → back to landing).
//!
//! Port reference: `insight/tools/maestro/flows/auth/logout.yaml`
//! & subflows `launch-fresh.yaml` / `login.yaml` / `logout.yaml` /
//! `open-menu.yaml`. Drives a cold-launch → login (3-step form) →
//! open-menu drawer → scroll-until "Log out" → tap "Log out" → assert
//! landing screen ("Log in to Insight") visible.
//!
//! # Why this dogfood
//!
//! Logout is the only way an authenticated session can be torn down
//! purely from the in-app surface. It exercises the drawer-scroll pattern
//! (drawer is long enough that "Log out" is below the fold on most sims)
//! and validates the auth-state transition: home-tab → drawer → landing.
//! Combined with v3.17 c2 sibling `auth_landing` (landing-only assertions)
//! and a future `auth_login` (login-only), this closes the auth-flow
//! triangle: landing → login → home → logout → landing.
//!
//! # Reversibility
//!
//! Net-mutation: ends logged-out (landing screen). The flow starts with
//! `launch_fresh` (terminate + re-open; performs in-app logout if a prior
//! session was authenticated) so initial state is normalized to landing
//! regardless of caller's prior state. After this example runs, the next
//! dogfood that needs an authenticated session must perform login again.
//!
//! # 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>` 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.
//! `launch_fresh` below (local example helper, distinct from the SDK's
//! `App::launch_fresh`) still uses the visual approximation
//! (terminate, re-open, plus in-app logout if necessary) and does NOT exercise the
//! new SDK wipe path; for true wipe set the env var and route through
//! `Step::LaunchApp { clear_state: true }`. Env-switch step
//! (`btn-env-staging`) is also NOT replayed; relies on sim being
//! provisioned with env=staging-tokyo (sim-insight default per
//! scripts/dev/sim-up.ts).
//!
//! # E2E env vars (consumed by `login_form`)
//!
//! - `E2E_EMAIL`    — login email (required; no default)
//! - `E2E_ORG`      — organization name (required; no default)
//! - `E2E_PASSWORD` — login password (required; no default)
//!
//! If any of the three is unset, `login_form` returns an `ExpectationFailure`
//! with an explicit message so the failure is AI-readable rather than a
//! silent empty-string submit that the server would reject opaquely.

use smix_sdk::{
    App, ExpectationFailure, FailureInit, 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 logout (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_logout_flow(&app).await {
        Ok(()) => {
            println!("\n✓ auth logout flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c2 dogfood — auth_logout ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ auth logout flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood auth logout 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 (email → org → password)
/// driven by E2E_EMAIL / E2E_ORG / E2E_PASSWORD env vars. Starts from
/// landing screen ("Log in to Insight" CTA visible), ends with
/// `btn-open-menu` confirmed (authenticated home tab).
async fn login_form(app: &App) -> Result<(), ExpectationFailure> {
    let email = std::env::var("SMIX_E2E_EMAIL").map_err(|_| {
        ExpectationFailure::new(FailureInit {
            message: "login_form: E2E_EMAIL env var is required but unset".into(),
            ..Default::default()
        })
    })?;
    let org = std::env::var("SMIX_E2E_ORG").map_err(|_| {
        ExpectationFailure::new(FailureInit {
            message: "login_form: E2E_ORG env var is required but unset".into(),
            ..Default::default()
        })
    })?;
    let password = std::env::var("SMIX_E2E_PASSWORD").map_err(|_| {
        ExpectationFailure::new(FailureInit {
            message: "login_form: E2E_PASSWORD env var is required but unset".into(),
            ..Default::default()
        })
    })?;

    // G8 cap-gap closed in v4.0 c3+c4 (daemonProxySynthesize swift fix).
    println!("  login_form: landing → sign-in (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?;

    // step 1: email
    println!("  login_form: step 1 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?;

    // step 2: org — if select mode is showing, switch to normal input first
    println!("  login_form: step 2 org");
    if app.find(&text("Enter new organization")).await? {
        app.tap(&text("Enter new organization")).await?;
        tokio::time::sleep(Duration::from_millis(400)).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?;

    // step 3: password — direct fill secureTextEntry (smix fill works, no toggle)
    println!("  login_form: step 3 password");
    tokio::time::sleep(Duration::from_millis(400)).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(())
}

/// 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/logout.yaml`: open the drawer, scroll until
/// "Log out" is visible, tap it, then wait for landing.
async fn logout(app: &App) -> Result<(), ExpectationFailure> {
    println!("  open menu drawer");
    open_menu(app).await?;
    println!("  scroll until 'Log out' visible");
    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;
    }
    println!("  tap 'Log out' (drawer button → triggers confirm dialog)");
    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?;
    }
    println!("  wait landing screen");
    app.wait_for(&text("Log in to Insight"), Duration::from_secs(30))
        .await?;
    Ok(())
}

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

async fn auth_logout_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: launch_fresh already executed in main; landing screen expected");

    println!("step 2: login_form (E2E_EMAIL / E2E_ORG / E2E_PASSWORD)");
    login_form(app).await?;

    println!("step 3: logout (open drawer → scroll → tap 'Log out')");
    logout(app).await?;

    println!("step 4: assert landing 'Log in to Insight' visible");
    app.assert_visible(&text("Log in to Insight")).await?;

    Ok(())
}
