/**
 * USER_JOURNEYS.md §1 (First-time guest), §2 (Returning guest, same device),
 * §3 (Returning guest, new device). Covers the happy path through
 * /join, the PIN modal, the onboarding overlay landing, and the
 * name-already-taken recovery transformation.
 */
import { test, expect } from '../../fixtures/test';
import { JoinPage } from '../../page-objects';
import { readStorage, STORAGE_KEYS, clearAllStorage } from '../../helpers/storage-helpers';

test.describe('Auth — join flow', () => {
  test('happy path: name → PIN modal → feed @smoke', async ({ page }) => {
    const join = new JoinPage(page);
    await join.goto();

    // The join form's landing state. There is no "Willkommen!" heading — the wedding
    // redesign (f243bfe) split it into a "Willkommen bei" lead-in plus the event name as
    // the <h1>, and this assertion was never updated, so it had been failing since.
    // Anchor on the testid the markup provides rather than on copy.
    await expect(page.getByTestId('join-event-name')).toBeVisible();

    const { pin } = await join.joinAs('Alice');
    expect(pin).toMatch(/^\d{4}$/);

    // PIN copy button toggles to "Kopiert!" on click
    await join.pinCopyButton.click();
    await expect(join.pinCopyButton).toHaveText(/Kopiert/i);

    await join.continueToFeed();
    await expect(page).toHaveURL(/\/feed$/);

    const storage = await readStorage(page);
    expect(storage.jwt, 'JWT in localStorage').toMatch(/^eyJ/);
    expect(storage.pin).toBe(pin);
    expect(storage.userId).toMatch(/^[0-9a-f-]{36}$/);
    expect(storage.displayName).toBe('Alice');
  });

  test('returning guest with valid JWT is redirected to /feed', async ({ page, guest, signIn }) => {
    const alice = await guest('Bob');
    await signIn(page, alice);

    // Now visit the root — should auto-redirect to /feed.
    await page.goto('/');
    await page.waitForURL('**/feed', { timeout: 5_000 });
  });

  test('returning guest, new device: same name shows the inline recovery form', async ({
    page,
    guest,
  }) => {
    const original = await guest('Charlie');

    // Brand-new browser context (cleared storage) — landing on /join with same name
    await clearAllStorage(page);
    const join = new JoinPage(page);
    await join.goto();
    await join.fillName('Charlie');
    await join.submit();

    await expect(join.recoveryPinInput).toBeVisible();
    await expect(page.getByText(/Charlie.*bereits vergeben/)).toBeVisible();

    // Type correct PIN → land on /feed with a new JWT. Filling the 4th digit
    // auto-submits (see pin-auto-submit.spec), so an explicit submit click would
    // race the navigation; click only as a fallback if the button is still around.
    await join.recoveryPinInput.fill(original.pin);
    if (await join.recoverySubmit.isEnabled().catch(() => false)) {
      await join.recoverySubmit.click().catch(() => {});
    }
    await page.waitForURL('**/feed');

    const storage = await readStorage(page);
    expect(storage.userId).toBe(original.userId);
    expect(storage.pin).toBe(original.pin);
  });

  test('repeated wrong PINs are throttled without locking the guest out', async ({
    page,
    guest,
    db,
  }) => {
    const dave = await guest('Dave');
    await clearAllStorage(page);

    const join = new JoinPage(page);
    await join.goto();
    await join.fillName('Dave');
    await join.submit();
    await expect(join.recoveryPinInput).toBeVisible();

    // Wrong PIN (real one is dave.pin), four times — one more than the OLD lock threshold of 3.
    // Typed digit by digit so the 4th character auto-submits (see pin-auto-submit.spec.ts);
    // clicking as well would double-submit and race the disabled state of the button.
    const wrong = dave.pin === '0000' ? '1111' : '0000';
    for (let i = 0; i < 4; i++) {
      await join.recoveryPinInput.fill('');
      await join.recoveryPinInput.pressSequentially(wrong, { delay: 30 });
      await expect(join.recoveryError).toBeVisible();
      await expect(join.recoverySubmit).toBeEnabled();
    }

    // THE PROPERTY THIS TEST EXISTS FOR, stated the way a guest experiences it: Dave can still
    // get into his own account.
    //
    // The lock threshold used to be 3, BELOW the per-(IP, name) ceiling — so these very
    // keystrokes locked Dave out for 15 minutes, and anyone who can read his name off the feed
    // could do it to him on repeat. Rate limits are disabled in this environment (see
    // config `rate_limits_enabled`), so what is exercised here is purely the account-lock tier;
    // the throttle tier is covered in 07-adversarial/auth-tampering.spec.ts.
    expect(
      await db.isPinLocked(dave.userId),
      'four wrong PINs from one device must not lock a guest out of their own account'
    ).toBe(false);

    await join.recoveryPinInput.fill('');
    await join.recoveryPinInput.pressSequentially(dave.pin, { delay: 30 });
    await page.waitForURL('**/feed');
  });

  test('"Anderen Namen wählen" returns to the normal join form', async ({ page, guest }) => {
    await guest('Eve');
    await clearAllStorage(page);

    const join = new JoinPage(page);
    await join.goto();
    await join.fillName('Eve');
    await join.submit();
    await expect(join.recoveryPinInput).toBeVisible();

    await join.tryDifferentNameButton.click();
    await expect(join.nameInput).toBeVisible();
    await expect(join.recoveryPinInput).not.toBeVisible();
  });

  test('"Ich habe bereits einen Account" link routes to /recover', async ({ page }) => {
    const join = new JoinPage(page);
    await join.goto();
    await join.linkToRecover.click();
    await expect(page).toHaveURL(/\/recover$/);
  });

  test('JWT and PIN keys are exactly the ones auth.ts expects', async ({ page, guest, signIn }) => {
    const handle = await guest('Frank');
    await signIn(page, handle);

    // Read raw localStorage to make sure no test accidentally uses a different key.
    const raw = await page.evaluate(() => ({
      jwt: localStorage.getItem('eventsnap_jwt'),
      pin: localStorage.getItem('eventsnap_pin'),
      userId: localStorage.getItem('eventsnap_user_id'),
      displayName: localStorage.getItem('eventsnap_display_name'),
    }));
    expect(raw.jwt).toBe(handle.jwt);
    expect(raw.pin).toBe(handle.pin);
    expect(raw.userId).toBe(handle.userId);
    expect(raw.displayName).toBe('Frank');
    // Sanity: the keys we just checked match STORAGE_KEYS in storage-helpers.ts
    expect(STORAGE_KEYS.jwt).toBe('eventsnap_jwt');
  });
});
