import { describe, it, expect, beforeEach } from 'vitest'
import { http } from 'msw'
import { server } from './msw/server'
import { jsonResponse, noContent } from './msw/helpers'
import { renderWithProviders, screen, waitFor, userEvent } from './helpers/render'
import { seedBearer, clearBearer } from './helpers/auth'
import { AdminPage } from '../src/features/admin'

// AZ-466 — Destructive UX policy (cross-component half)
//
// AC-1 (FT-P-26): clicking Delete on a class → confirming → DELETE fires AFTER confirm.
// AC-2 (FT-N-07): clicking Delete → Cancel → NO DELETE fires.
// AC-4 (FT-P-27 / NFT-SEC-08): static check enumerates every destructive surface
//                              and asserts each one mounts a `<ConfirmDialog>`.
//                              The static side lives in `scripts/run-tests.sh` /
//                              `scripts/check-banned-deps.mjs` (`STC-SEC8`).
//                              The runtime mirror is one of the cases below.
// AC-5 (NFT-SEC-07): no `alert()` in `src/`. Static side enforces this; runtime
//                    side here only documents the current allowlist. The runtime
//                    test would require renderring every component that calls
//                    alert — out of black-box scope. Static check `STC-SEC7`
//                    handles enforcement.
//
// Production drift (`src/features/admin/AdminPage.tsx:30-33` and table row
// line 76):
//   `handleDeleteClass` directly calls `api.delete` without gating through
//   `<ConfirmDialog>`. The class-delete row's `<button onClick=...>` triggers
//   the network mutation immediately. FT-P-26 + FT-N-07 are recorded as
//   `it.fails()` until production wraps `handleDeleteClass` behind ConfirmDialog
//   (Phase B feature task).

const SEED_CLASSES = [
  { id: 1, name: 'class-a', shortName: 'a', color: '#ff0000', maxSizeM: 7 },
  { id: 2, name: 'class-b', shortName: 'b', color: '#00ff00', maxSizeM: 5 },
]

interface CapturedDelete {
  url: string
  classId: string
}

function captureClassDelete(): { deletes: CapturedDelete[] } {
  const deletes: CapturedDelete[] = []
  server.use(
    http.delete('/api/admin/classes/:id', ({ request, params }) => {
      deletes.push({ url: new URL(request.url).pathname, classId: String(params.id) })
      return noContent()
    }),
    // AdminPage bootstrap: classes (annotations service), aircrafts, users.
    // NOTE: `AdminPage` reads `/api/admin/users` as a flat User[]
    // (`api.get<User[]>` then `users.map`) — but the suite-default MSW
    // wraps `seedUsers` in `paginate(...)`. That's a documented
    // production-vs-suite drift (admin handler should expose flat in dev).
    // For this destructive-UX test we only care about class-delete
    // wiring, so the override returns a flat empty array to keep
    // AdminPage from crashing on `users.map`.
    http.get('/api/annotations/classes', () => jsonResponse(SEED_CLASSES)),
    http.get('/api/flights/aircrafts', () => jsonResponse([])),
    http.get('/api/admin/users', () => jsonResponse([])),
    // AuthContext bootstraps with GET /api/admin/auth/refresh; tests using
    // <ProtectedRoute>-less render still mount AuthProvider. Return 401 so
    // the unauth path resolves quickly and bootstrap finishes.
    http.post('/api/admin/auth/refresh', () => new Response(null, { status: 401 })),
  )
  return { deletes }
}

describe('AZ-466 — Destructive UX policy (class-delete cross-component test)', () => {
  beforeEach(() => {
    seedBearer()
  })

  describe('AC-1 (FT-P-26) — happy path: Delete → Confirm → DELETE fires', () => {
    it.fails(
      'class-delete prompts a `<ConfirmDialog>` BEFORE issuing the DELETE',
      async () => {
        // Arrange
        const { deletes } = captureClassDelete()
        renderWithProviders(<AdminPage />)
        // Wait for the class table to populate.
        await screen.findByText('class-a')

        // Act — find the delete button on the first class row. AZ-512 added
        // an edit (✎) button alongside the delete (×); select by text.
        const rows = screen.getAllByText(/^class-/i)
        const firstRow = rows[0].closest('tr')!
        const deleteBtn = Array.from(firstRow.querySelectorAll('button')).find(b => b.textContent === '×')!
        await userEvent.click(deleteBtn)

        // Assert — a ConfirmDialog must appear before any DELETE fires.
        // Drift: AdminPage's `handleDeleteClass` issues api.delete directly
        // (no ConfirmDialog wired). The DELETE fires immediately and the
        // dialog never appears.
        const dialog = await screen.findByRole('dialog', undefined, { timeout: 1000 })
        expect(dialog).toBeInTheDocument()
        expect(deletes).toHaveLength(0)

        // Confirm via the dialog → DELETE fires now.
        const confirm = screen.getAllByRole('button').find(b => /confirm/i.test(b.textContent ?? ''))!
        await userEvent.click(confirm)
        await waitFor(() => expect(deletes).toHaveLength(1), { timeout: 1000 })
        clearBearer()
      },
    )

    it('control: production today bypasses ConfirmDialog and deletes immediately', async () => {
      // Pin the current (drift) one-click delete behavior. When AC-1 lands,
      // this control flips red and is removed.
      const { deletes } = captureClassDelete()
      renderWithProviders(<AdminPage />)
      await screen.findByText('class-a')

      const rows = screen.getAllByText(/^class-/i)
      const firstRow = rows[0].closest('tr')!
      const deleteBtn = Array.from(firstRow.querySelectorAll('button')).find(b => b.textContent === '×')!
      await userEvent.click(deleteBtn)

      await waitFor(() => expect(deletes).toHaveLength(1), { timeout: 1000 })
      expect(deletes[0].url).toMatch(/\/api\/admin\/classes\/\d+/)
      clearBearer()
    })
  })

  describe('AC-2 (FT-N-07) — cancel path: Delete → Cancel → NO DELETE fires', () => {
    it.fails(
      'class-delete with Cancel via the ConfirmDialog suppresses the DELETE entirely',
      async () => {
        // Arrange
        const { deletes } = captureClassDelete()
        renderWithProviders(<AdminPage />)
        await screen.findByText('class-a')

        // Act — click delete, then Cancel on the dialog. AZ-512 added an
        // edit (✎) button alongside the delete (×); select by text.
        const rows = screen.getAllByText(/^class-/i)
        const firstRow = rows[0].closest('tr')!
        const deleteBtn = Array.from(firstRow.querySelectorAll('button')).find(b => b.textContent === '×')!
        await userEvent.click(deleteBtn)

        // Drift: the dialog never appears today. The find call fails first
        // (no `role="dialog"` ever mounts), but even if it did, cancel would
        // need to suppress a DELETE that today already fired synchronously.
        const dialog = await screen.findByRole('dialog', undefined, { timeout: 1000 })
        expect(dialog).toBeInTheDocument()
        const cancel = screen.getAllByRole('button').find(b => /cancel/i.test(b.textContent ?? ''))!
        await userEvent.click(cancel)

        // Assert — NO DELETE was issued.
        await new Promise(r => setTimeout(r, 50))
        expect(deletes).toHaveLength(0)
        clearBearer()
      },
    )
  })

  describe('AC-4 (FT-P-27 / NFT-SEC-08) — destructive surfaces enumeration', () => {
    // The runtime side of FT-P-27 / NFT-SEC-08 is a multi-component static
    // walk. Implementing it as a Vitest test would require rendering every
    // production page and asserting every destructive surface mounts a
    // ConfirmDialog. That is the static check's job (`STC-SEC8` in
    // `scripts/run-tests.sh` calling `check-banned-deps.mjs --kind=destructive_unguarded`).
    // We pin one runtime example here (AdminPage's class-delete) above to
    // catch regressions on a known-current drift surface.
    it.skip(
      'QUARANTINE — full enumeration is enforced by STC-SEC8 (static check); per-surface runtime tests follow per-feature in Phase B',
      () => {},
    )
  })
})
