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

// AZ-458 — e2e variants of the SSE-lifecycle and bearer-rotation scenarios
// that require the real suite stack (live-GPS simulator embedded in the
// `flights/:test` image; annotation-status generator in `annotations/:test`).
//
//   FT-P-09 — annotation-status SSE opens on <AnnotationsPage> mount
//             (QUARANTINE — production AnnotationsPage opens no SSE today)
//   FT-P-18 — live-GPS SSE opens within 5 s of flight select
//   NFT-PERF-03 — bearer-rotation reconnect ≤ 5 s after a refresh
//   NFT-RES-02  — bearer rotation reconnects both live-GPS and annotation-status
//                 within 5 s (QUARANTINE for annotation-status; live-GPS portion
//                 documents the AC-2 drift — passes once production reconnects
//                 the EventSource on token rotation).
//
// Profile: e2e (gated by docker compose). Skipped in fast/host runs.
//
// Black-box discipline: assertions inspect the network surface (which
// `text/event-stream` requests opened/closed and when) and the DOM where
// live-GPS values land. The tests do NOT import production modules.

const ALICE_EMAIL = 'op_alice@test.local'
const ALICE_PASSWORD = 'TestPassword!23'

async function login(page: import('@playwright/test').Page) {
  await page.goto('/login')
  await page.getByLabel(/email/i).fill(ALICE_EMAIL)
  await page.getByLabel(/password/i).fill(ALICE_PASSWORD)
  await Promise.all([
    page.waitForResponse(
      (r) => r.url().includes('/api/admin/auth/login') && r.request().method() === 'POST',
    ),
    page.getByRole('button', { name: /sign in/i }).click(),
  ])
}

test.describe('AZ-458 e2e — SSE lifecycle + bearer rotation', () => {
  test('FT-P-18 / NFT-PERF-04 — live-GPS SSE opens within 5 s of flight select', async ({ page }) => {
    test.setTimeout(20_000)
    await login(page)
    await page.goto('/flights')

    // Switch the side panel to GPS mode and select a flight.
    await page.getByRole('button', { name: /gps/i }).click()

    const ssePromise = page.waitForRequest(
      (req) => /\/api\/flights\/[^/]+\/live-gps/.test(req.url()),
      { timeout: 5_000 },
    )
    await page.getByRole('button', { name: /select flight/i }).click()
    await page.getByRole('button', { name: /flight-1|recon alpha/i }).first().click()

    const req = await ssePromise

    // Assert AC-1: bearer is in the URL per ADR-008 (?access_token=...).
    expect(req.url()).toMatch(/[?&]access_token=[A-Za-z0-9._-]+/)
  })

  test('FT-P-19 / NFT-PERF-05 — live-GPS SSE closes within 1 s of deselect', async ({ page }) => {
    test.setTimeout(20_000)
    await login(page)
    await page.goto('/flights')
    await page.getByRole('button', { name: /gps/i }).click()
    await page.getByRole('button', { name: /select flight/i }).click()
    await page.getByRole('button', { name: /flight-1|recon alpha/i }).first().click()

    // Capture the EventSource on the page so the test can observe close().
    const closedAt = await page.evaluate(async () => {
      const original = window.EventSource
      let lastClosed = -1
      const proxy = new Proxy(original, {
        construct(target, args) {
          const inst = new target(...(args as ConstructorParameters<typeof EventSource>))
          const origClose = inst.close.bind(inst)
          inst.close = () => {
            lastClosed = performance.now()
            origClose()
          }
          return inst
        },
      })
      window.EventSource = proxy as unknown as typeof EventSource
      return new Promise<number>((resolve) => {
        // Wait up to 5 s for the close to land.
        const start = performance.now()
        const tick = () => {
          if (lastClosed > 0) resolve(lastClosed - start)
          else if (performance.now() - start > 5000) resolve(-1)
          else requestAnimationFrame(tick)
        }
        // Trigger the deselect from the test side via DOM.
        const evt = new CustomEvent('e2e-deselect')
        window.dispatchEvent(evt)
        tick()
      })
    })

    // Simulate "deselect" — for the contract test we go back to the params
    // tab which makes the FlightsPage useEffect tear down the SSE (per
    // FlightsPage.tsx:65-68 — the effect deps include `mode`).
    await page.getByRole('button', { name: /params/i }).click()

    expect(closedAt, 'EventSource close() should fire within 1 s of deselect').toBeLessThan(1000)
  })

  test('NFT-PERF-03 / NFT-RES-02 — live-GPS SSE reconnects with the new bearer within 5 s of rotation (AC-2 drift)', async ({ page }) => {
    test.setTimeout(30_000)
    test.fail(
      true,
      'AC-2 drift: FlightsPage useEffect deps do not include the bearer, so SSE does not reconnect on token rotation. Test passes once the production effect re-runs on token change.',
    )

    await login(page)
    await page.goto('/flights')
    await page.getByRole('button', { name: /gps/i }).click()
    await page.getByRole('button', { name: /select flight/i }).click()
    await page.getByRole('button', { name: /flight-1|recon alpha/i }).first().click()

    const firstReq = await page.waitForRequest(
      (req) => /\/api\/flights\/[^/]+\/live-gps/.test(req.url()),
      { timeout: 5_000 },
    )
    const firstUrl = firstReq.url()

    // Trigger a refresh via the test-only endpoint that rotates the bearer.
    // The admin/:test image exposes /api/admin/test-only/rotate-bearer (matches
    // the convention used by /api/admin/test-only/reset). If absent, this is
    // the moment to surface a stack-side gap.
    const rotated = await page.request.post('/api/admin/test-only/rotate-bearer').catch(() => null)
    expect(rotated?.ok(), 'admin/:test must expose /test-only/rotate-bearer').toBeTruthy()

    // Drive AuthContext to absorb the new bearer (refresh path).
    await page.evaluate(async () => {
      await fetch('/api/admin/auth/refresh', { credentials: 'include' })
    })

    const secondReq = await page.waitForRequest(
      (req) => /\/api\/flights\/[^/]+\/live-gps/.test(req.url()) && req.url() !== firstUrl,
      { timeout: 5_000 },
    )

    expect(secondReq.url()).toMatch(/[?&]access_token=[A-Za-z0-9._-]+/)
    expect(secondReq.url()).not.toEqual(firstUrl)
  })

  test.skip('FT-P-09 / NFT-PERF-06 — annotation-status SSE opens on mount + closes within 1 s of unmount', () => {
    // QUARANTINE: src/features/annotations/AnnotationsPage.tsx does not open
    // any SSE today. Once an annotation-status subscription is added, this
    // test follows the same shape as FT-P-18/FT-P-19 above but targets
    // /api/annotations/.../status (or whatever the production URL ends up
    // being). Leaving the assertion shape here as a planning anchor:
    //
    //   await login(page)
    //   const annSsePromise = page.waitForRequest(
    //     (req) => /\/api\/annotations\/.*\/status/.test(req.url()),
    //   )
    //   await page.goto('/annotations')
    //   await annSsePromise
  })
})
