//! v3.17 c2 — dogfood usecase: auth login error (wrong password → error toast).
//!
//! Port reference: `insight/tools/maestro/flows/auth/login-error.yaml`
//! & `insight/tools/maestro/subflows/launch-fresh.yaml`. Drives a
//! cold-launch path to the landing screen, then walks the three-step
//! sign-in form (email → org → password) with a deliberately wrong
//! password, and asserts the resulting error toast / message appears
//! within 30s.
//!
//! Flow stages:
//! 1. `launch_fresh` — terminate + deep-link re-open + (logout if needed)
//!    to reach the landing screen.
//! 2. tap "Log in to Insight" CTA to enter the sign-in stack.
//! 3. step 1 email — tap `input-email`, clear, type `${E2E_EMAIL}`,
//!    press Return, tap "Continue", wait for org-step landmark text
//!    ("Enter new organization" or "Organization name").
//! 4. step 2 org — tap optional "Enter new organization" (switches
//!    select-mode → input-mode if visible), tap `input-org`, clear,
//!    type `${E2E_ORG}`, press Return, tap "Continue", wait for
//!    `input-password`.
//! 5. step 3 password — tap `input-password`, type literal
//!    "wrong_password_12345" directly into the secureTextEntry field
//!    (smix fill works on secure fields — G11 false alarm), hideKeyboard,
//!    tap `btn-login`.
//! 6. assert error surface "Error|login failed" visible within 30s.
//!
//! # Why this dogfood
//!
//! Auth failure path is a product-spec critical surface — a regression
//! that silently swallows wrong-credential errors (e.g. toast wiring
//! broken, i18n key dropped, error boundary catches without rendering)
//! would let users sit on an unresponsive button. maestro covers it
//! with 50 lines of yaml; smix must port the contract end-to-end so
//! any future bundle regression that loses the error message trips
//! the dogfood immediately.
//!
//! # Reversibility
//!
//! Pure read once the wrong password is submitted — server rejects,
//! local form state is unchanged, no persistent side-effect. End state
//! is the password screen with an error toast (or banner). Cleanup by
//! the next `launch_fresh` in a subsequent dogfood is sufficient.
//!
//! # E2E env vars
//!
//! - `E2E_EMAIL`      — required, real account email (org lookup must succeed).
//! - `E2E_ORG`        — required, real org slug (so we reach the password
//!   step; otherwise the test exits at the org step and never exercises
//!   the error surface we care about).
//! - `E2E_PASSWORD`   — NOT used. Password is hardcoded to
//!   "wrong_password_12345" so we deterministically hit the error path
//!   regardless of the real value.
//! - `SMIX_RUNNER_PORT` (default 22087), `SMIX_UDID`, `INSIGHT_BUNDLE_ID`
//!   — standard smix dogfood plumbing.
//!
//! # 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
//! → 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 }`.
//!
//! Practical implication for this flow: if a prior test left an
//! org/email pre-populated in MMKV form-state, the step-1 `eraseText`
//! equivalent (`app.clear`) covers the visible field, but any
//! background autofill on RN-managed inputs could in theory re-inject
//! stale text. The wrong-password literal is intentionally distinctive
//! so a stale-password regression would still hit a real auth failure
//! (just for the wrong reason) — fine for the error-surface assertion.

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 error (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());

    let email = std::env::var("SMIX_E2E_EMAIL").map_err(
        |_| "SMIX_E2E_EMAIL env var required (real account email for org lookup to succeed)",
    )?;
    let org = std::env::var("SMIX_E2E_ORG")
        .map_err(|_| "SMIX_E2E_ORG env var required (real org slug to reach password step)")?;

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

    match auth_login_error_flow(&app, &email, &org).await {
        Ok(()) => {
            println!("\n✓ auth login error flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c2 dogfood — auth_login_error ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ auth login error flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood auth login error 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(())
}

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

async fn auth_login_error_flow(
    app: &App,
    email: &str,
    org: &str,
) -> Result<(), ExpectationFailure> {
    // G8 cap-gap closed in v4.0 c3+c4 (daemonProxySynthesize swift fix).
    println!("step 1: tap landing CTA 'Log in to Insight' (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?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 2: email — tap input-email, clear, type E2E_EMAIL, Return, Continue");
    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?;

    println!("step 3: wait org-step landmark ('Enter new organization|Organization name', ≤30s)");
    app.wait_for(
        &text_regex("Enter new organization|Organization name"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 4: org — optional tap 'Enter new organization' (select→input mode switch)");
    if app.find(&text("Enter new organization")).await? {
        let _ = app.tap(&text("Enter new organization")).await;
        tokio::time::sleep(Duration::from_millis(500)).await;
    }

    println!("step 5: org — tap input-org, clear, type E2E_ORG, Return, Continue");
    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?;

    println!("step 6: wait input-password visible (≤30s)");
    app.wait_for(&id("input-password"), Duration::from_secs(30))
        .await?;

    println!(
        "step 7: password — settle before direct fill secureTextEntry (smix fill works — G11 false alarm)"
    );
    tokio::time::sleep(Duration::from_millis(300)).await;

    println!("step 8: password — tap input-password, type literal wrong_password_12345");
    app.tap(&id("input-password")).await?;
    // NOTE: hardcoded literal (NOT read from E2E_PASSWORD). The whole
    // point of this dogfood is to deterministically hit the auth-failure
    // path; pulling a real password from env would defeat that.
    app.fill(&id("input-password"), "wrong_password_12345")
        .await?;

    println!("step 9: hideKeyboard + tap btn-login (submit wrong credentials)");
    app.hide_keyboard().await?;
    app.tap(&id("btn-login")).await?;

    println!("step 10: assert error surface 'Error|login failed' visible (≤30s)");
    app.wait_for(&text_regex("Error|login failed"), Duration::from_secs(30))
        .await?;

    Ok(())
}
