/**
 * USER_JOURNEYS.md §12 — release the export, see status, download.
 *
 * We don't drive a real export job here (the compression takes too long
 * for E2E timing). Instead we forge the export-job rows via the db helper
 * and assert the API behavior + UI banner state.
 */
import { test, expect } from '../../fixtures/test';
import { ExportPage } from '../../page-objects';

const SLUG = 'e2e-test-event';

test.describe('Export — release and download', () => {
  test('/export shows the "not yet available" state before release', async ({
    page,
    guest,
    signIn,
  }) => {
    const g = await guest('PreRelease');
    await signIn(page, g);
    const exportPage = new ExportPage(page);
    await exportPage.goto();

    // POSITIVE anchor first. An absence-only assertion ("no download button") is green on a
    // blank page, a 404, or an unhydrated shell — i.e. it would pass even with the buttons
    // rendered, if the locator were wrong. Pin the empty state we actually expect.
    await expect(exportPage.notAvailableBanner).toBeVisible({ timeout: 10_000 });

    // And only THEN the absence: no download affordance exists before release. This uses the
    // real button label ("Download"), so rendering a download button here turns it red.
    await expect(exportPage.downloadButtons).toHaveCount(0);
  });

  test('/export shows enabled download buttons once released and the jobs are done', async ({
    page,
    guest,
    signIn,
    db,
  }) => {
    const g = await guest('PostRelease');
    await db.setExportReleased(SLUG, true);
    await db.fakeExportJob(SLUG, 'zip', 'done');
    await db.fakeExportJob(SLUG, 'html', 'done');

    await signIn(page, g);
    const exportPage = new ExportPage(page);
    await exportPage.goto();

    // The mirror of the test above: this is what proves the "before release" locators can
    // actually SEE a download button when one exists. Without this, a typo'd locator makes
    // the pre-release test unfalsifiable.
    await expect(exportPage.notAvailableBanner).toHaveCount(0);
    await expect(exportPage.zipDownloadButton).toBeVisible({ timeout: 10_000 });
    await expect(exportPage.zipDownloadButton).toBeEnabled();
    await expect(exportPage.htmlDownloadButton).toBeVisible();
    await expect(exportPage.htmlDownloadButton).toBeEnabled();
  });

  test('export status API reflects released flag', async ({ guest, db }) => {
    const g = await guest('ReleaseQuery');

    let res = await fetch(
      (process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/export/status',
      {
        headers: { Authorization: `Bearer ${g.jwt}` },
      }
    );
    let body: any = await res.json();
    expect(body.released).toBe(false);

    await db.setExportReleased(SLUG, true);
    await db.fakeExportJob(SLUG, 'zip', 'done');
    await db.fakeExportJob(SLUG, 'html', 'done');

    res = await fetch(
      (process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/export/status',
      {
        headers: { Authorization: `Bearer ${g.jwt}` },
      }
    );
    body = await res.json();
    expect(body.released).toBe(true);
    expect(body.zip.status).toBe('done');
    expect(body.html.status).toBe('done');
  });

  const base = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101';

  // Browser downloads stream to disk via a top-level navigation, so the download
  // endpoint authenticates with a single-use ticket (no Bearer header).
  /** The raw mint response — `/export/ticket` pre-validates that the archive is actually
   *  servable, so an unavailable keepsake is refused HERE rather than after charging one of the
   *  guest's three daily downloads. */
  async function mintTicketResponse(jwt: string, kind: 'zip' | 'html' = 'zip') {
    return fetch(base + `/api/v1/export/ticket?kind=${kind}`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${jwt}` },
    });
  }

  test('ZIP download 404s for a `done` job at a RETIRED epoch', async ({ guest, db }) => {
    const g = await guest('NotReady');
    // The event is released and the zip job says `done` — but the job carries a dead epoch, which
    // is exactly the state a reopen (or a superseded worker) leaves behind. Readiness is derived
    // from `job.epoch = event.export_epoch`, so this archive is NOT current and must never be
    // served. A 200 here would be the stale-keepsake bug leaking through the read path.
    await db.setExportReleased(SLUG, true);
    await db.fakeExportJob(SLUG, 'zip', 'done');
    await db.setExportZipReady(SLUG, false); // retire the job to a dead epoch

    // Refused at the MINT. This used to be asserted one step later, on the download, because the
    // spec did not send `kind` and so skipped the pre-check entirely — now that a ticket is bound
    // to an archive the kind is always known, and the guest is told the truth before a daily
    // download is spent on an archive that cannot be served.
    expect((await mintTicketResponse(g.jwt)).status).toBe(404);
  });

  test('ZIP download 404s when the job is current but the file is missing on disk', async ({
    guest,
    db,
  }) => {
    const g = await guest('ReadyNoFile');
    // Released, and the job is `done` at the LIVE epoch — but no archive was ever written (we
    // never ran a real export). The handler must 404 on the missing file, not 200/500 or serve
    // something stale.
    await db.setExportReleased(SLUG, true);
    await db.fakeExportJob(SLUG, 'zip', 'done');
    await db.setExportZipReady(SLUG, true);

    // Same as above: the pre-check resolves the file on disk, so a `done` job whose archive is
    // missing is refused at the mint rather than 404ing mid-download.
    expect((await mintTicketResponse(g.jwt)).status).toBe(404);
  });
});
