import { Buffer } from 'node:buffer';
import { expect, test, type Page } from './fixtures';

// The finished-game export: one signed relative URL (game.export_url) resolved against
// the app's own origin, delivered by the most native affordance per platform — Telegram's
// showPopup chooser + downloadFile dialog (all bridge calls, activation-safe), VK's
// native viewer/download, the OS share sheet with the fetched file on a mobile browser,
// a plain anchor download on desktop. g3 ("vs Rick") is the seeded finished game.
// The mock gateway returns a shape-faithful /dl/... path; the tests intercept that
// route and serve bytes, so the download itself is exercised end to end.

// A minimal valid 1×1 PNG, hex-decoded without node typings (the e2e tsconfig has none).
const PNG_HEX =
  '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489' +
  '0000000a49444154789c6360000000020001e221bc330000000049454e44ae426082';
// Buffer, not Uint8Array: route.fulfill silently hangs a FETCH interception on a
// Uint8Array body (a navigation download tolerates it) — the share test deadlocked.
const PNG_BYTES = Buffer.from(PNG_HEX, 'hex');

async function routeDl(page: Page): Promise<void> {
  await page.route('**/dl/**', (route) => {
    const png = route.request().url().includes('/png');
    return route.fulfill({
      status: 200,
      contentType: png ? 'image/png' : 'text/plain; charset=utf-8',
      headers: { 'Content-Disposition': 'attachment' },
      body: png ? PNG_BYTES : '#character-encoding UTF-8\n',
    });
  });
}

async function openFinishedHistory(page: Page): Promise<void> {
  await page.goto('/');
  await page.getByRole('button', { name: /guest/i }).click();
  await page.getByRole('button', { name: /Rick/ }).click();
  await expect(page.locator('[data-cell]').first()).toBeVisible();
  await page.locator('.scoreboard').click();
  await expect(page.getByRole('button', { name: 'Export game' })).toBeVisible();
}

// Desktop WebKit (Safari's engine) exposes a working Web Share API, unlike Chromium and WebKit on
// Linux (the CI host). The "plain desktop browser" delivery paths tested below are reached only when
// Web Share is ABSENT — shareUrlAsFile / pickGcgDelivery fall through to the anchor download or the
// clipboard copy — so pin that precondition; otherwise macOS WebKit takes the share branch and no
// download or clipboard copy occurs. Mirrors the inverse stub the share-sheet test sets up.
async function withoutWebShare(page: Page): Promise<void> {
  await page.addInitScript(() => {
    Object.defineProperty(navigator, 'share', { value: undefined, configurable: true });
    Object.defineProperty(navigator, 'canShare', { value: undefined, configurable: true });
  });
}

test('the chooser downloads the PNG through the signed URL on a plain browser', async ({ page }) => {
  await withoutWebShare(page);
  await routeDl(page);
  await openFinishedHistory(page);
  await page.getByRole('button', { name: 'Export game' }).click();

  const download = page.waitForEvent('download');
  await page.getByRole('button', { name: 'Image (PNG)' }).click();
  expect((await download).suggestedFilename()).toMatch(/^game-.+\.png$/);
});

test('the chooser downloads the GCG through the same signed-URL route', async ({ page }) => {
  await withoutWebShare(page);
  await routeDl(page);
  await openFinishedHistory(page);
  await page.getByRole('button', { name: 'Export game' }).click();

  const download = page.waitForEvent('download');
  await page.getByRole('button', { name: 'GCG file' }).click();
  expect((await download).suggestedFilename()).toMatch(/^game-.+\.gcg$/);
});

test('inside Telegram the chooser is the native popup and delivery the native downloadFile', async ({
  page,
}) => {
  // A Telegram stub with showPopup AND downloadFile (Bot API 8.0): the whole chain stays
  // in bridge calls — the popup picks the image, the artifact goes to the native download
  // dialog, never an in-webview anchor. (Activation-safe: no gesture-gated Web APIs.)
  await page.addInitScript(() => {
    Object.assign(window, {
      Telegram: {
        WebApp: {
          initData: 'query_id=test&user=%7B%22id%22%3A1%7D&auth_date=1&hash=deadbeef',
          initDataUnsafe: {},
          platform: 'android',
          ready() {},
          expand() {},
          showPopup(params: { buttons?: { id?: string; type?: string }[] }, cb: (id: string) => void) {
            const w = window as unknown as { __popup?: unknown };
            w.__popup = params;
            setTimeout(() => cb('png'), 0);
          },
          downloadFile(params: { url: string; file_name: string }) {
            const w = window as unknown as { __downloads?: { url: string; file_name: string }[] };
            (w.__downloads ??= []).push(params);
          },
        },
      },
    });
  });
  await page.goto('/');
  await expect(page.getByText('Your turn')).toBeVisible(); // auto-authenticated lobby
  await page.getByRole('button', { name: /Rick/ }).click();
  await expect(page.locator('[data-cell]').first()).toBeVisible();
  await page.locator('.scoreboard').click();
  await page.getByRole('button', { name: 'Export game' }).click();

  // The native popup carried both formats plus cancel, and no in-app modal was mounted.
  const popup = await page.evaluate(
    () => (window as unknown as { __popup?: { buttons?: { id?: string; type?: string }[] } }).__popup,
  );
  expect(popup?.buttons?.map((b) => b.id ?? b.type)).toEqual(['png', 'gcg', 'cancel']);
  await expect(page.getByRole('button', { name: 'GCG file' })).toHaveCount(0);

  await expect
    .poll(() =>
      page.evaluate(() => (window as unknown as { __downloads?: { url: string; file_name: string }[] }).__downloads),
    )
    .toHaveLength(1);
  const dl = await page.evaluate(
    () => (window as unknown as { __downloads: { url: string; file_name: string }[] }).__downloads[0],
  );
  // The relative signed path was resolved against the app's own origin — absolute for TG.
  expect(dl.url).toMatch(/^http.+\/dl\/.+\/png\?/);
  expect(dl.file_name).toMatch(/^game-.+\.png$/);
});

test('a mobile browser gets the OS share sheet with the fetched file', async ({ page }) => {
  // Web Share with files present (a mobile browser): the artifact is fetched from the
  // signed URL and handed to navigator.share as a File — the sheet opens directly,
  // nothing lands in Downloads first.
  await page.addInitScript(() => {
    const shared: { name: string; type: string }[] = [];
    Object.defineProperty(navigator, 'canShare', { value: () => true, configurable: true });
    Object.defineProperty(navigator, 'share', {
      value: async (data: { files?: File[] }) => {
        for (const f of data.files ?? []) shared.push({ name: f.name, type: f.type });
      },
      configurable: true,
    });
    (window as unknown as { __shared: typeof shared }).__shared = shared;
  });
  await routeDl(page);
  await openFinishedHistory(page);
  await page.getByRole('button', { name: 'Export game' }).click();
  await page.getByRole('button', { name: 'Image (PNG)' }).click();

  await expect
    .poll(() => page.evaluate(() => (window as unknown as { __shared: unknown[] }).__shared))
    .toHaveLength(1);
  const shared = await page.evaluate(
    () => (window as unknown as { __shared: { name: string; type: string }[] }).__shared[0],
  );
  expect(shared.name).toMatch(/^game-.+\.png$/);
  expect(shared.type).toBe('image/png');
});

test('Telegram on iOS keeps the app chooser and opens the OS share sheet', async ({ page }) => {
  // TG iOS: WKWebView has navigator.share, and the sheet is the preferred delivery — but
  // it needs the user activation a native-popup callback cannot provide, so the chooser
  // stays the app modal on this one platform.
  await page.addInitScript(() => {
    const shared: { name: string; type: string }[] = [];
    Object.defineProperty(navigator, 'canShare', { value: () => true, configurable: true });
    Object.defineProperty(navigator, 'share', {
      value: async (data: { files?: File[] }) => {
        for (const f of data.files ?? []) shared.push({ name: f.name, type: f.type });
      },
      configurable: true,
    });
    (window as unknown as { __shared: typeof shared }).__shared = shared;
    Object.assign(window, {
      Telegram: {
        WebApp: {
          initData: 'query_id=test&user=%7B%22id%22%3A1%7D&auth_date=1&hash=deadbeef',
          initDataUnsafe: {},
          platform: 'ios',
          ready() {},
          expand() {},
          showPopup() {
            (window as unknown as { __popupCalled?: boolean }).__popupCalled = true;
          },
          downloadFile() {
            (window as unknown as { __downloadCalled?: boolean }).__downloadCalled = true;
          },
        },
      },
    });
  });
  await routeDl(page);
  await page.goto('/');
  await expect(page.getByText('Your turn')).toBeVisible();
  await page.getByRole('button', { name: /Rick/ }).click();
  await expect(page.locator('[data-cell]').first()).toBeVisible();
  await page.locator('.scoreboard').click();
  await page.getByRole('button', { name: 'Export game' }).click();

  // The app modal opened (both formats) — the native popup was not used…
  await expect(page.getByRole('button', { name: 'Image (PNG)' })).toBeVisible();
  await page.getByRole('button', { name: 'Image (PNG)' }).click();
  // …and the artifact went to the OS share sheet, not TG's download dialog.
  await expect
    .poll(() => page.evaluate(() => (window as unknown as { __shared: unknown[] }).__shared))
    .toHaveLength(1);
  const flags = await page.evaluate(() => {
    const w = window as unknown as { __popupCalled?: boolean; __downloadCalled?: boolean };
    return { popup: !!w.__popupCalled, download: !!w.__downloadCalled };
  });
  expect(flags).toEqual({ popup: false, download: false });
});

test('a legacy Telegram client (no downloadFile) hides the image and copies the GCG', async ({ page }) => {
  await withoutWebShare(page);
  await page.addInitScript(() => {
    // Headless engines deny the real clipboard; a permissive stub keeps the legacy
    // copy path deterministic in both browsers.
    Object.defineProperty(navigator, 'clipboard', {
      value: { writeText: async () => {} },
      configurable: true,
    });
    Object.assign(window, {
      Telegram: {
        WebApp: {
          initData: 'query_id=test&user=%7B%22id%22%3A1%7D&auth_date=1&hash=deadbeef',
          initDataUnsafe: {},
          ready() {},
          expand() {},
        },
      },
    });
  });
  await page.goto('/');
  await expect(page.getByText('Your turn')).toBeVisible();
  await page.getByRole('button', { name: /Rick/ }).click();
  await expect(page.locator('[data-cell]').first()).toBeVisible();
  await page.locator('.scoreboard').click();
  await page.getByRole('button', { name: 'Export game' }).click();

  // No image option without the native download; the GCG stays available (clipboard path).
  await expect(page.getByRole('button', { name: 'GCG file' })).toBeVisible();
  await expect(page.getByRole('button', { name: 'Image (PNG)' })).toHaveCount(0);
  await page.getByRole('button', { name: 'GCG file' }).click();
  await expect(page.getByText('GCG copied to the clipboard.')).toBeVisible();
});
