import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { http, HttpResponse } from 'msw'
import { server } from './msw/server'
import { jsonResponse } from './msw/helpers'
import { renderWithProviders, screen, waitFor, userEvent } from './helpers/render'
import { seedBearer, clearBearer } from './helpers/auth'
import { SettingsPage } from '../src/features/settings'
import { seedAircraft } from './fixtures/seed_aircraft'
import type { SystemSettings, DirectorySettings } from '../src/types'

// AZ-477 — Settings save resilience + 2 s error budget.
//
// AC-1 (FT-N-13 / NFT-RES-05) — 500 recovery: PUT 500 ⇒ saving flag clears
//                               (Save button enabled again) AND a DOM error
//                               region (role="alert") is visible within 2 s.
// AC-2 (FT-N-14 / NFT-RES-06) — Network drop: same two conditions on
//                               HttpResponse.error().
// AC-3 (NFT-PERF-09)         — Deadline: wall-clock from PUT response/error
//                               to error visibility ≤ 2 s.
//
// v2 SettingsPage wraps `save()` in try/catch/finally and renders an inline
// role="alert" in the sticky footer when the PUT rejects. The three contract
// tests below assert that wiring directly.

const SYSTEM_SEED: SystemSettings = {
  id: 'sys-1',
  name: 'Unit Alpha',
  militaryUnit: 'A-1',
  defaultCameraWidth: 1920,
  defaultCameraFoV: 60,
  thumbnailWidth: 256,
  thumbnailHeight: 256,
  thumbnailBorder: 1,
  generateAnnotatedImage: false,
  silentDetection: false,
}

const DIRS_SEED: DirectorySettings = {
  id: 'dirs-1',
  videosDir: '/data/videos',
  imagesDir: '/data/images',
  labelsDir: '/data/labels',
  resultsDir: '/data/results',
  thumbnailsDir: '/data/thumbnails',
  gpsSatDir: '/data/gps/sat',
  gpsRouteDir: '/data/gps/route',
}

interface SettingsRig {
  systemPuts: number
  /** Wall-clock instant the PUT handler returned its (failing) response. */
  responseAt: { value: number | null }
}

type SettingsFailure = { kind: 'http'; status: number } | { kind: 'network' }

function rigSettingsEnv(failure: SettingsFailure): SettingsRig {
  let systemPuts = 0
  const responseAt = { value: null as number | null }

  server.use(
    http.post('/api/admin/auth/refresh', () => new Response(null, { status: 401 })),
    http.get('/api/annotations/settings/system', () => jsonResponse(SYSTEM_SEED)),
    http.get('/api/annotations/settings/directories', () => jsonResponse(DIRS_SEED)),
    http.get('/api/flights/aircrafts', () => jsonResponse(seedAircraft)),
    http.put('/api/annotations/settings/system', () => {
      systemPuts += 1
      responseAt.value = performance.now()
      if (failure.kind === 'http') {
        return new HttpResponse('upstream failure', { status: failure.status })
      }
      return HttpResponse.error()
    }),
  )

  return { get systemPuts() { return systemPuts }, responseAt }
}

/**
 * SettingsPage (v2) renders a single sticky-footer "Save Changes" button that
 * persists whichever panels are dirty in parallel. The footer button is the
 * only Save affordance; per-panel Save buttons no longer exist. We must mark
 * the Tenant panel as dirty by editing a field before the footer button
 * becomes enabled — selecting the Military Unit input by accessible name and
 * typing a single character is enough to flip the dirty flag.
 */
async function findSystemSaveButton(): Promise<HTMLElement> {
  // Wait until the data has loaded (heading is present immediately, but the
  // input is rendered only after the GET resolves).
  await screen.findByRole('heading', { name: /Tenant Configuration/i })
  return screen.getByRole('button', { name: /^Save Changes$/i })
}

async function makeTenantDirty(): Promise<void> {
  const militaryUnit = await screen.findByLabelText(/Military Unit/i)
  await userEvent.type(militaryUnit, '!')
}

async function renderAndClickSave(): Promise<void> {
  renderWithProviders(<SettingsPage />)
  await makeTenantDirty()
  const saveButton = await findSystemSaveButton()
  await userEvent.click(saveButton)
}

describe('AZ-477 — Settings save resilience + 2 s error budget', () => {
  beforeEach(() => {
    seedBearer()
  })

  afterEach(() => {
    clearBearer()
  })

  describe('AC-1 (FT-N-13 / NFT-RES-05) — 500 recovery', () => {
    it('PUT 500 → Save button is no longer disabled within 2 s', async () => {
      rigSettingsEnv({ kind: 'http', status: 500 })
      await renderAndClickSave()
      const saveButton = await findSystemSaveButton()
      await waitFor(
        () => expect(saveButton).not.toBeDisabled(),
        { timeout: 2000 },
      )
    })

    it('PUT 500 → an in-DOM error region (role="alert") appears within 2 s', async () => {
      rigSettingsEnv({ kind: 'http', status: 500 })
      await renderAndClickSave()
      const alertEl = await screen.findByRole('alert', {}, { timeout: 2000 })
      // Message shape: production task picks the i18n key; the test only
      // asserts that *some* user-visible error text is present.
      expect((alertEl.textContent ?? '').trim().length).toBeGreaterThan(0)
    })
  })

  describe('AC-2 (FT-N-14 / NFT-RES-06) — network drop', () => {
    it('network error → Save button is no longer disabled within 2 s', async () => {
      rigSettingsEnv({ kind: 'network' })
      await renderAndClickSave()
      const saveButton = await findSystemSaveButton()
      await waitFor(
        () => expect(saveButton).not.toBeDisabled(),
        { timeout: 2000 },
      )
    })

    it('network error → an in-DOM error region (role="alert") appears within 2 s', async () => {
      rigSettingsEnv({ kind: 'network' })
      await renderAndClickSave()
      const alertEl = await screen.findByRole('alert', {}, { timeout: 2000 })
      expect((alertEl.textContent ?? '').trim().length).toBeGreaterThan(0)
    })
  })

  describe('AC-3 (NFT-PERF-09) — deadline ≤ 2 s', () => {
    it('500 → DOM error region visible within 2000 ms of the response', async () => {
      const rig = rigSettingsEnv({ kind: 'http', status: 500 })
      await renderAndClickSave()
      const alertEl = await screen.findByRole('alert', {}, { timeout: 2500 })
      const alertVisibleAt = performance.now()
      expect(rig.responseAt.value).not.toBeNull()
      const elapsed = alertVisibleAt - (rig.responseAt.value as number)
      // Elapsed must be ≥ 0 (response landed first) AND ≤ 2000 ms.
      expect(elapsed).toBeGreaterThanOrEqual(0)
      expect(elapsed).toBeLessThanOrEqual(2000)
      expect(alertEl).toBeInTheDocument()
    })
  })
})
