import { expect, test, type Page } from './fixtures';

// The two-tier client-version gate (docs/ARCHITECTURE.md §2). The mock transport never produces a real
// version signal, so — like the maintenance overlay — the e2e drives both tiers through the
// window.__update hook (gateway.ts, mock-only): __update.on() raises the HARD "update required" notice
// (the net-state machine's offlineVersionLocked), __update.recommend() raises the SOFT "update
// available" nudge.

async function enterLobby(page: Page): Promise<void> {
  await page.getByRole('button', { name: /guest|гост/i }).first().click();
  await expect(page.locator('button.tab').nth(2)).toBeVisible();
}

test('the update-required notice covers the app and offers Update or Play offline', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('alertdialog')).toHaveCount(0);

  await page.evaluate(() => (window as unknown as { __update: { on(): void } }).__update.on());
  const notice = page.getByRole('alertdialog');
  await expect(notice).toBeVisible();
  await expect(notice.getByRole('button', { name: /Update|Обновить/i })).toBeVisible();
  await expect(notice.getByRole('button', { name: /Play offline|Играть офлайн/i })).toBeVisible();
});

test('the Update action reloads the web client', async ({ page }) => {
  await page.goto('/');
  await page.evaluate(() => (window as unknown as { __update: { on(): void } }).__update.on());
  const notice = page.getByRole('alertdialog');
  await expect(notice).toBeVisible();

  // On the web the action reloads the page to fetch the current client (a native build opens the store
  // instead). The reload re-bootstraps to INITIAL online, so the notice is gone and the login is back.
  await Promise.all([
    page.waitForEvent('load'),
    notice.getByRole('button', { name: /Update|Обновить/i }).click(),
  ]);
  await expect(page.getByRole('alertdialog')).toHaveCount(0);
  await expect(page.getByRole('button', { name: /guest/i })).toBeVisible();
});

test('Play offline dismisses the notice into the offline lobby', async ({ page }) => {
  await page.goto('/');
  await enterLobby(page);
  // Online first: the seeded online game (vs Ann) is listed.
  await expect(page.getByText('Ann', { exact: false }).first()).toBeVisible();

  // A too-old foreground call locks the version: the notice covers the (now offline) app.
  await page.evaluate(() => (window as unknown as { __update: { on(): void } }).__update.on());
  const notice = page.getByRole('alertdialog');
  await expect(notice).toBeVisible();

  // "Play offline" dismisses the notice and leaves the app in the offline lobby (the version lock is an
  // offline state): the header is blue and the online game is gone.
  await notice.getByRole('button', { name: /Play offline|Играть офлайн/i }).click();
  await expect(page.getByRole('alertdialog')).toHaveCount(0);
  await expect(page.locator('header.nav.offline')).toBeVisible();
  // The server game (vs Ann) rides along greyed from the cache (offline, un-openable).
  await expect(page.locator('.rowwrap.greyed').filter({ hasText: 'Ann' })).toBeVisible();
});

test('the soft update-available nudge shows in the lobby and dismisses', async ({ page }) => {
  await page.goto('/');
  await enterLobby(page);
  await expect(page.locator('.nudge')).toHaveCount(0);

  // A served response below the recommended version carries X-Update-Recommended: the non-blocking
  // nudge appears in the lobby (above the tab bar); play is unaffected — no overlay.
  await page.evaluate(() => (window as unknown as { __update: { recommend(): void } }).__update.recommend());
  const nudge = page.locator('.nudge');
  await expect(nudge).toBeVisible();
  await expect(nudge).toContainText(/Update available|Доступно обновление/i);
  await expect(page.getByRole('alertdialog')).toHaveCount(0);

  // Dismissing it (the ×) hides it for the session.
  await nudge.getByRole('button', { name: /Close|Закрыть/i }).click();
  await expect(page.locator('.nudge')).toHaveCount(0);
});
