// @ts-nocheck
/**
 * E2E test: Complete login → onboarding → home flow via deep link (Linux / tauri-driver).
 *
 * Verifies the full auth + onboarding journey using mock data:
 *   Phase 1 — Deep link authentication:
 *     1. `openhuman://auth?token=...` deep link is triggered via __simulateDeepLink
 *     2. App calls POST /telegram/login-tokens/:token/consume  (mock server)
 *     3. App receives JWT, dispatches to Redux authSlice
 *     4. UserProvider calls GET /auth/me  (mock server)
 *
 *   Phase 2 — Onboarding steps (3 steps in Onboarding.tsx):
 *     Step 0: WelcomeStep            — "Continue"
 *     Step 1: SkillsStep             — "Continue" or "Skip for Now"
 *     Step 2: ContextGatheringStep   — user-driven gate: "Start when ready" / "Continue" /
 *                                       "Skip for now" (skipped entirely if no sources connected)
 *
 *   Phase 3 — Completion verification:
 *     - App calls POST /settings/onboarding-complete (from SkillsStep)
 *     - App navigates to #/home — greeting with mock user's name shown
 *
 *   Phase 4 — Error paths:
 *     - Expired token returns 401 and app does not navigate to home
 *     - Invalid token returns 401 and app does not navigate to home
 *
 *   Phase 5 — Bypass auth path:
 *     - `openhuman://auth?token=...&key=auth` sets token directly (no consume call)
 *
 * The mock server runs on http://127.0.0.1:18473 and the .app bundle must
 * have been built with VITE_BACKEND_URL pointing there.
 */
import { waitForApp, waitForAppReady, waitForAuthBootstrap } from '../helpers/app-helpers';
import { buildBypassJwt, triggerAuthDeepLink, triggerDeepLink } from '../helpers/deep-link-helpers';
import {
  clickText,
  dumpAccessibilityTree,
  hasAppChrome,
  textExists,
  waitForWebView,
  waitForWindowVisible,
} from '../helpers/element-helpers';
import {
  clearRequestLog,
  getRequestLog,
  resetMockBehavior,
  setMockBehavior,
  startMockServer,
  stopMockServer,
} from '../mock-server';

/**
 * Poll the mock server request log until a matching request appears.
 */
async function waitForRequest(method, urlFragment, timeout = 15_000) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    const log = getRequestLog();
    const match = log.find(r => r.method === method && r.url.includes(urlFragment));
    if (match) return match;
    await browser.pause(500);
  }
  return undefined;
}

/**
 * Wait until one of the candidate texts appears on screen.
 * Returns the matched text or null on timeout.
 */
async function waitForAnyText(candidates, timeout = 15_000) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    for (const text of candidates) {
      if (await textExists(text)) return text;
    }
    await browser.pause(500);
  }
  return null;
}

/**
 * Click the first matching text from a list of candidates.
 * Returns the clicked text or null if none found.
 */
async function clickFirstMatch(candidates, timeout = 5_000) {
  for (const text of candidates) {
    if (await textExists(text)) {
      await clickText(text, timeout);
      return text;
    }
  }
  return null;
}

/**
 * Verify Redux auth state via browser.execute (tauri-driver only).
 */
async function getReduxAuthState() {
  try {
    return await browser.execute(() => {
      // Redux store is exposed on window.__REDUX_DEVTOOLS_EXTENSION__
      // but we can read from localStorage where redux-persist stores auth
      const persistedAuth = localStorage.getItem('persist:auth');
      if (persistedAuth) {
        try {
          return JSON.parse(persistedAuth);
        } catch {
          return null;
        }
      }
      return null;
    });
  } catch {
    return null;
  }
}

// Track whether onboarding was walked through in the UI so Phase 3 can
// decide whether to require the onboarding-complete backend call.
let hadOnboardingWalkthrough = false;

describe('Login flow — complete with mock data (Linux)', () => {
  before(async () => {
    await startMockServer();
    await waitForApp();
    clearRequestLog();
    hadOnboardingWalkthrough = false;
  });

  after(async () => {
    resetMockBehavior();
    await stopMockServer();
  });

  // -----------------------------------------------------------------------
  // Phase 1: Deep link authentication
  // -----------------------------------------------------------------------

  it('app process is running and has a window handle', async () => {
    const hasChrome = await hasAppChrome();
    expect(hasChrome).toBe(true);
  });

  it('deep link triggers login and shows the app window', async () => {
    await triggerAuthDeepLink('e2e-test-token');

    await waitForWindowVisible(25_000);
    await waitForWebView(15_000);
    await waitForAppReady(15_000);
    await waitForAuthBootstrap(15_000);
  });

  it('mock server received the token-consume call', async () => {
    const call = await waitForRequest('POST', '/telegram/login-tokens/', 20_000);
    if (!call) {
      console.log(
        '[LoginFlow] Missing consume call. Request log:',
        JSON.stringify(getRequestLog(), null, 2)
      );
    }
    expect(call).toBeDefined();
  });

  it('mock server received the user-profile call', async () => {
    const deadline = Date.now() + 15_000;
    let call;
    while (Date.now() < deadline) {
      const log = getRequestLog();
      call = log.find(
        r => r.method === 'GET' && (r.url.includes('/auth/me') || r.url.includes('/settings'))
      );
      if (call) break;
      await browser.pause(500);
    }
    if (!call) {
      console.log('[LoginFlow] Request log:', JSON.stringify(getRequestLog(), null, 2));
    }
    expect(call).toBeDefined();
  });

  it('Redux auth state has a token after login', async () => {
    const authState = await getReduxAuthState();
    if (authState) {
      const token =
        typeof authState.token === 'string' ? authState.token.replace(/^"|"$/g, '') : null;
      console.log('[LoginFlow] Redux auth token present:', !!token);
      expect(token).toBeTruthy();
    } else {
      console.log('[LoginFlow] Could not read Redux auth state (persist format may differ)');
      // Non-fatal: the token-consume mock call was verified above
    }
  });

  // -----------------------------------------------------------------------
  // Phase 2: Onboarding (real step walkthrough)
  //
  // Onboarding.tsx renders as a portal overlay. On tauri-driver (Linux),
  // browser.execute() works, so we can interact with the WebView DOM.
  //
  // Steps in order:
  //   0: WelcomeStep            — "Continue" button
  //   1: SkillsStep             — "Continue" or "Skip for Now"
  //   2: ContextGatheringStep   — user-driven gate: intro card with "Start when ready" /
  //       "Continue" / "Skip for now". Step is skipped entirely when SkillsStep
  //       produced zero connected sources (Onboarding.tsx → handleSkillsNext).
  // -----------------------------------------------------------------------

  it('onboarding overlay or home page is visible', async () => {
    await browser.pause(3_000);

    // Real onboarding step markers
    const onboardingCandidates = [
      'Welcome', // WelcomeStep heading
      'Skip', // Onboarding defer button (top-right)
      'Continue', // WelcomeStep CTA
    ];
    const homeCandidates = ['Home', 'Skills', 'Conversations'];

    const foundOnboarding = await waitForAnyText(onboardingCandidates, 5_000);
    if (foundOnboarding) {
      console.log(`[LoginFlow] Onboarding visible: "${foundOnboarding}"`);
    }

    const foundHome = !foundOnboarding ? await waitForAnyText(homeCandidates, 5_000) : null;
    if (foundHome) {
      console.log(
        `[LoginFlow] Home page visible: "${foundHome}" (onboarding may be deferred/completed)`
      );
    }

    expect(foundOnboarding || foundHome).toBeTruthy();
  });

  it('walk through onboarding steps (if overlay is visible)', async () => {
    // Check if we're on the WelcomeStep or any onboarding step
    const onboardingVisible =
      (await textExists('Welcome')) ||
      (await textExists('Skip')) ||
      (await textExists('Continue')) ||
      (await textExists('Finish Setup'));

    if (!onboardingVisible) {
      console.log('[LoginFlow] Onboarding overlay not visible — skipping step walkthrough');
      hadOnboardingWalkthrough = false;
      return;
    }

    hadOnboardingWalkthrough = true;

    // Step 0: WelcomeStep — click "Continue"
    if (await textExists('Welcome')) {
      const clicked = await clickFirstMatch(['Continue'], 10_000);
      console.log(`[LoginFlow] WelcomeStep: clicked "${clicked}"`);
      await browser.pause(2_000);
    }

    // Step 1: SkillsStep — click "Skip for Now" (no skills connected in E2E)
    {
      const skillsVisible = await textExists('Connect Gmail');
      if (skillsVisible) {
        const clicked = await clickFirstMatch(['Skip for Now', 'Continue'], 10_000);
        if (clicked) {
          console.log(`[LoginFlow] SkillsStep: clicked "${clicked}"`);
          await browser.pause(3_000);
        }
      }
    }

    // Step 2: ContextGatheringStep — intro gate. Heading is "Getting to know you"
    // (pre-start) or "Reading your connected accounts" / "Context Ready" (post-start).
    // We don't actually want the real LinkedIn enrichment pipeline to run in E2E
    // (it would hit the Rust core), so prefer "Skip for now" when present.
    // "Continue" covers both the no-Gmail branch (skipped stages render Continue
    // immediately after Start) and the completed-pipeline final state.
    {
      const contextVisible =
        (await textExists('Getting to know you')) ||
        (await textExists('Reading your connected accounts')) ||
        (await textExists('Context Ready'));
      if (contextVisible) {
        const clicked = await clickFirstMatch(
          ['Skip for now', 'Continue', 'Start when ready'],
          10_000
        );
        if (clicked) {
          console.log(`[LoginFlow] ContextGatheringStep: clicked "${clicked}"`);
          await browser.pause(3_000);
        }
      }
    }
  });

  // -----------------------------------------------------------------------
  // Phase 3: Verify completion
  // -----------------------------------------------------------------------

  it('mock server received the onboarding-complete call (if onboarding was walked)', async () => {
    if (!hadOnboardingWalkthrough) {
      console.log(
        '[LoginFlow] Onboarding was not walked (overlay not visible) — skipping assertion'
      );
      return;
    }

    const log = getRequestLog();
    // The app calls POST /settings/onboarding-complete (via userApi.onboardingComplete)
    // The mock may handle it at /telegram/settings/onboarding-complete or /settings/onboarding-complete
    const call = log.find(
      r =>
        r.method === 'POST' &&
        (r.url.includes('/settings/onboarding-complete') ||
          r.url.includes('/telegram/settings/onboarding-complete'))
    );
    if (call) {
      console.log('[LoginFlow] onboarding-complete call verified');
    } else {
      // The call may go through the core sidecar RPC relay rather than direct HTTP,
      // so it might not appear in the mock request log. Log but don't fail.
      console.log(
        '[LoginFlow] onboarding-complete call not in mock log (may have gone through core RPC)'
      );
      console.log('[LoginFlow] Request log:', JSON.stringify(log, null, 2));
    }
  });

  it('app navigated to Home page after onboarding', async () => {
    const nameCandidates = [
      'Test',
      'Good morning',
      'Good afternoon',
      'Good evening',
      'Message OpenHuman',
      'Upgrade to Premium',
    ];

    const foundText = await waitForAnyText(nameCandidates, 15_000);

    if (foundText) {
      console.log(`[LoginFlow] Home page confirmed: found "${foundText}"`);
    } else {
      const tree = await dumpAccessibilityTree();
      console.log('[LoginFlow] Home page text not found. Tree:\n', tree.slice(0, 4000));
    }

    expect(foundText).not.toBeNull();
  });

  // -----------------------------------------------------------------------
  // Phase 4: Error paths — expired and invalid tokens
  // -----------------------------------------------------------------------

  it('expired token triggers consume call that returns 401', async () => {
    // Note: The app is already authenticated from Phase 1-3. In a single-instance
    // Tauri desktop app, we cannot fully reset the in-memory Redux state between
    // tests. This test verifies that the expired token deep link triggers the
    // consume call and the mock rejects it with 401.
    clearRequestLog();
    setMockBehavior('token', 'expired');

    await triggerDeepLink('openhuman://auth?token=expired-test-token');
    await browser.pause(5_000);

    // Verify the consume call was made (mock returns 401 for expired tokens)
    const call = await waitForRequest('POST', '/telegram/login-tokens/', 10_000);
    expect(call).toBeDefined();
    console.log('[LoginFlow] Expired token: consume call made (mock returns 401)');

    // The app should not have navigated away — prior session remains intact.
    // We verify the deep link handler attempted the consume and it was rejected.
    resetMockBehavior();
  });

  it('invalid token triggers consume call that returns 401', async () => {
    clearRequestLog();
    setMockBehavior('token', 'invalid');

    await triggerDeepLink('openhuman://auth?token=invalid-test-token');
    await browser.pause(5_000);

    // Verify the consume call was made (mock returns 401 for invalid tokens)
    const call = await waitForRequest('POST', '/telegram/login-tokens/', 10_000);
    expect(call).toBeDefined();
    console.log('[LoginFlow] Invalid token: consume call made (mock returns 401)');

    resetMockBehavior();
  });

  // -----------------------------------------------------------------------
  // Phase 5: Bypass auth path (key=auth)
  // -----------------------------------------------------------------------

  it('bypass auth deep link sets token directly without consume call', async () => {
    // Clear auth state so we start unauthenticated — prevents stale session
    clearRequestLog();
    resetMockBehavior();
    await browser.execute(() => {
      localStorage.removeItem('persist:auth');
      window.location.hash = '/';
    });
    await browser.pause(2_000);

    const bypassJwt = buildBypassJwt('e2e-bypass-user');

    // Trigger bypass deep link (key=auth skips token consume)
    await triggerDeepLink(`openhuman://auth?token=${encodeURIComponent(bypassJwt)}&key=auth`);
    await browser.pause(5_000);

    // Assert NO consume call was made (bypass skips it)
    const consumeCall = getRequestLog().find(
      r => r.method === 'POST' && r.url.includes('/telegram/login-tokens/')
    );
    expect(consumeCall).toBeUndefined();
    console.log('[LoginFlow] Bypass auth: no consume call (correct — token set directly)');

    // Assert the app navigated to home (post-login UI marker)
    const homeCandidates = [
      'Good morning',
      'Good afternoon',
      'Good evening',
      'Message OpenHuman',
      'Home',
    ];
    const foundHome = await waitForAnyText(homeCandidates, 15_000);
    expect(foundHome).not.toBeNull();
    console.log(`[LoginFlow] Bypass auth: home reached with "${foundHome}"`);

    // Auth slice persistence moved away from a standalone persist:auth key.
    // Home-route confirmation above is the stable assertion that bypass auth succeeded.
    console.log(
      '[LoginFlow] Bypass auth: home route reached (token persistence format is implementation-specific)'
    );
  });
});
