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

// AZ-471 — e2e companion for FT-P-39 (manual bounding-box draw).
//
// The fast suite covers all 5 ACs in JSDOM with deterministic canvas
// instrumentation. This e2e companion is the FT-P-39 (manual draw) row
// only — task spec marks it `fast + e2e`. The other rows (FT-P-40/41/42/43)
// stay fast-only because Playwright's pointer event timing makes pixel-
// perfect anchor invariance harder to assert than the JSDOM spy already
// does.
//
// Discipline: black-box. We observe the DOM (canvas pixels via
// canvas.toDataURL) and the network (annotation save POST), never React
// internals. The drift documented in the fast suite (Ctrl+drag pan,
// Ctrl+wheel zoom-around-cursor, Ctrl+click multi-select) is NOT re-asserted
// here — those are state-machine drifts and the fast tests pin them.

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

async function login(page: import('@playwright/test').Page): Promise<void> {
  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-471 — CanvasEditor manual draw (e2e companion)', () => {
  test('FT-P-39 — manual bbox draw produces a save with one detection', async ({
    page,
    browserName,
  }) => {
    test.skip(
      browserName !== 'chromium',
      'Pointer event timing on Firefox makes draw assertions noisy; fast suite covers it',
    )
    test.setTimeout(30_000)

    await login(page)
    await page.goto('/annotations')

    // Need a media item selected for the canvas to mount with a backing
    // image. If the suite seed has no media, the test reports the gap
    // explicitly rather than masking the contract.
    const canvas = page.locator('canvas').first()
    if (!(await canvas.isVisible({ timeout: 5000 }).catch(() => false))) {
      test.skip(true, 'Suite seed has no media available for annotation')
    }

    // Capture annotation save POSTs so we can assert one detection lands
    // on the wire after the user-driven draw.
    const saves: Array<{ url: string; body: string | null }> = []
    await page.route('**/api/annotations/annotations**', async (route) => {
      const req = route.request()
      if (req.method() === 'POST') {
        saves.push({ url: req.url(), body: req.postData() })
      }
      await route.continue()
    })

    const box = await canvas.boundingBox()
    expect(box, 'canvas must have a bounding box').not.toBeNull()
    if (!box) return

    // Draw a bbox spanning ~30 % → ~60 % of the canvas width / height. Use
    // mouse.down + steps + mouse.up to drive a real drag — a single move
    // wouldn't trigger the pointer-move handlers reliably.
    const x1 = box.x + box.width * 0.30
    const y1 = box.y + box.height * 0.30
    const x2 = box.x + box.width * 0.60
    const y2 = box.y + box.height * 0.60

    await page.mouse.move(x1, y1)
    await page.mouse.down()
    await page.mouse.move(x2, y2, { steps: 12 })
    await page.mouse.up()

    // After the draw, look for a Save affordance and click it. CanvasEditor
    // saves are gated by the page Save button (per AZ-460 e2e). If no Save
    // button is visible, the SPA may auto-save — flush via a navigation tick
    // and inspect the recorded POSTs.
    const saveBtn = page.getByRole('button', { name: /^Save$/i }).first()
    if (await saveBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
      await saveBtn.click().catch(() => null)
    }

    await page.waitForTimeout(750)

    expect(saves.length, 'manual draw must produce at least one save POST').toBeGreaterThan(0)
    const lastSave = saves[saves.length - 1]
    expect(lastSave.url).toContain('/api/annotations/annotations')
    if (lastSave.body) {
      const parsed = JSON.parse(lastSave.body) as { detections?: unknown[] }
      expect(Array.isArray(parsed.detections)).toBe(true)
      expect((parsed.detections ?? []).length).toBeGreaterThan(0)
    }
  })
})
