import { test, expect } from '@playwright/test'

// AZ-476 — e2e companion for the 500 MB upload cap.
//
// AC-1 (FT-N-06 / NFT-RES-07): nginx returns 413 on a 501 MB upload; SPA
//                              renders an in-DOM error region carrying an
//                              i18n-keyed message. Today production silently
//                              swallows the failure and falls through to
//                              local-mode (drift) — `test.fail()` until the
//                              error region is wired.
// AC-2 (no alert):             The 413 path does NOT invoke `alert()`. Today
//                              this passes vacuously (no error path runs at
//                              all). The fast-profile test pins both contracts
//                              against MSW; this e2e companion exercises the
//                              real nginx body-size limit set in the suite.
//
// Requires the suite docker-compose stack (`e2e/docker-compose.suite-e2e.yml`).
// Skips with a clear reason on developer hosts without the stack.

const BIG_BYTES = 501 * 1024 * 1024

function buildOversizedBuffer(): Buffer {
  // Spec requires a 501 MB sparse zero-filled payload. Buffer.alloc is the
  // cheapest in-memory way to build it; 501 MB sits well below the 1 GB
  // Node default heap on CI runners. If a future runner downsizes its heap,
  // switch this fixture to a temp file produced by `dd`.
  return Buffer.alloc(BIG_BYTES)
}

test.describe('AZ-476 — upload 501 MB → 413 (e2e companion)', () => {
  test.fail(
    'AC-1 (FT-N-06 / NFT-RES-07) — 501 MB upload surfaces in-DOM error',
    async ({ page }) => {
      // Capture every batch upload response so we can verify nginx really
      // returned 413 (and not the SPA short-circuiting on the client side).
      const batchResponses: { url: string; status: number }[] = []
      page.on('response', async (resp) => {
        const u = resp.url()
        if (/\/api\/annotations\/media\/batch(\?|$)/.test(u)) {
          batchResponses.push({ url: u, status: resp.status() })
        }
      })

      await page.goto('/annotations')

      // The "Open File" input is hidden behind a label; Playwright's
      // setInputFiles works directly on the input element regardless of CSS
      // visibility.
      const fileInput = page.locator('input[type="file"]').nth(1)
      if (!(await fileInput.count())) {
        test.skip(true, 'Suite UI did not render the upload input')
      }

      await fileInput.setInputFiles({
        name: 'huge_recon_video.mp4',
        mimeType: 'video/mp4',
        buffer: buildOversizedBuffer(),
      })

      // Wait for the 413 to come back. If nginx in this stack is configured
      // with a different cap, the test reports the configuration mismatch
      // explicitly rather than masking the contract.
      await page
        .waitForFunction(
          (checkUrl) => {
            type Win = Window & { __batchStatuses?: number[] }
            const w = window as Win
            void checkUrl
            return Array.isArray(w.__batchStatuses) && w.__batchStatuses.includes(413)
          },
          'noop',
          { timeout: 30_000 },
        )
        .catch(() => null)

      // Fallback assertion — page-side wait may not see the response. Use
      // the response listener accumulator we set up above.
      const sawThirteen = batchResponses.some((r) => r.status === 413)
      if (!sawThirteen) {
        test.skip(
          true,
          `Suite nginx did not return 413 for a 501 MB upload (saw: ${
            batchResponses.map((r) => r.status).join(',') || 'no /batch responses'
          })`,
        )
      }

      // Contract assertion — drift today. Will pass once production wires the
      // toast + i18n key for the 413 path.
      const alertEl = page.getByRole('alert').first()
      await expect(alertEl).toBeVisible({ timeout: 5000 })
      await expect(alertEl).toContainText(/too large|exceeds|413/i)
    },
  )

  test('AC-2 — the 413 path does NOT invoke window.alert()', async ({ page }) => {
    // Track every dialog. Playwright auto-dismisses dialogs after listeners
    // are attached, so a stray `alert()` shows up here as a "dialog" event.
    const dialogs: string[] = []
    page.on('dialog', async (dialog) => {
      dialogs.push(`${dialog.type()}:${dialog.message()}`)
      await dialog.dismiss().catch(() => null)
    })

    await page.goto('/annotations')

    const fileInput = page.locator('input[type="file"]').nth(1)
    if (!(await fileInput.count())) {
      test.skip(true, 'Suite UI did not render the upload input')
    }

    await fileInput.setInputFiles({
      name: 'huge_recon_video.mp4',
      mimeType: 'video/mp4',
      buffer: buildOversizedBuffer(),
    })

    // Allow the 413 round-trip + any error-handling React render to settle.
    await page.waitForTimeout(2000)

    // Filter for alert() specifically — confirm() and prompt() are out of
    // scope for this AC, but we still want to know if either fires.
    const alertDialogs = dialogs.filter((d) => d.startsWith('alert:'))
    expect(alertDialogs).toEqual([])
  })
})
