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

// AZ-457 — e2e variants for auth-surface scenarios that require the real
// suite stack (admin/auth/login + admin/auth/refresh).
//
//   FT-P-02   — 401 → refresh → retry against the real admin/ service
//   NFT-SEC-01 — bearer never written to localStorage / sessionStorage post-login
//   NFT-SEC-02 — document.cookie does not expose the refresh token value
//   NFT-SEC-03 — refresh cookie attributes Secure; HttpOnly; SameSite=Strict
//
// Profile: e2e (gated by docker compose stack — Risk 4 in AZ-456). Skipped
// when running locally without the suite stack.
//
// Black-box discipline: every assertion is at the network, browser-storage,
// or DOM surface. The tests do NOT import production modules (e2e bodies
// only touch Playwright primitives).
//
// Seed login: `op_alice@test.local` is in fixtures/seeds.sql and the
// admin/:test image accepts the test password set by ENABLE_TEST_ONLY_ENDPOINTS.

const ALICE_EMAIL = 'op_alice@test.local'
const ALICE_PASSWORD = 'TestPassword!23'   // matches admin/:test seed password

test.describe('AZ-457 e2e — auth surface', () => {
  test('FT-P-02 (rows 03, 12): 401 → refresh → retry against real admin/', async ({ page }) => {
    // Arrange — login via the real service and capture the bearer.
    await page.goto('/login')
    const loginResponse = await Promise.all([
      page.waitForResponse(
        (r) => r.url().includes('/api/admin/auth/login') && r.request().method() === 'POST',
      ),
      page.getByLabel(/email/i).fill(ALICE_EMAIL).then(() =>
        page.getByLabel(/password/i).fill(ALICE_PASSWORD),
      ).then(() => page.getByRole('button', { name: /sign in/i }).click()),
    ])
    expect(loginResponse[0].status()).toBe(200)

    // Force the next /users/me call to 401, then let the retry succeed.
    let firstHit = true
    let refreshHits = 0
    await page.route('**/api/admin/users/me', async (route) => {
      if (firstHit) {
        firstHit = false
        await route.fulfill({ status: 401 })
        return
      }
      await route.continue()
    })
    await page.route('**/api/admin/auth/refresh', async (route) => {
      refreshHits += 1
      await route.continue()
    })

    // Act — trigger an authed call. Any navigated-to admin page exercises
    // /api/admin/users/me on mount through the production AuthContext.
    await page.goto('/admin')

    // Assert — exactly one refresh observed, original request retried, page
    // reaches an authed surface (defensive: just verify no /login redirect).
    await expect.poll(() => refreshHits, { timeout: 10_000 }).toBe(1)
    await expect(page).not.toHaveURL(/\/login$/)
  })

  test('NFT-SEC-01 (row 04) + NFT-SEC-02 (row 05): bearer not in storage; refresh-cookie not in document.cookie', async ({ page, context }) => {
    // Arrange — full login flow.
    await page.goto('/login')
    await page.getByLabel(/email/i).fill(ALICE_EMAIL)
    await page.getByLabel(/password/i).fill(ALICE_PASSWORD)
    const loginResp = 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(),
    ])
    const responseBody = await loginResp[0].json()
    const bearer: string = responseBody.token
    expect(bearer.length).toBeGreaterThan(0)

    // Wait for the post-login route to settle.
    await page.waitForLoadState('networkidle')

    // NFT-SEC-01 — neither localStorage nor sessionStorage contains the bearer.
    const stored = await page.evaluate(() => {
      const out: Record<'local' | 'session', Record<string, string>> = {
        local: {},
        session: {},
      }
      for (let i = 0; i < localStorage.length; i += 1) {
        const k = localStorage.key(i)!
        out.local[k] = localStorage.getItem(k) ?? ''
      }
      for (let i = 0; i < sessionStorage.length; i += 1) {
        const k = sessionStorage.key(i)!
        out.session[k] = sessionStorage.getItem(k) ?? ''
      }
      return out
    })
    const flat = JSON.stringify(stored)
    expect(flat, 'bearer leaked to localStorage / sessionStorage').not.toContain(bearer)

    // NFT-SEC-02 — JS-visible document.cookie does not expose the refresh token.
    const jsCookies = await page.evaluate(() => document.cookie)
    expect(jsCookies).not.toMatch(/refresh/i)
    expect(jsCookies).not.toContain(bearer)

    // Defence-in-depth: the actual refresh cookie IS present in the browser jar
    // (HttpOnly is invisible to JS but visible via context.cookies()).
    const allCookies = await context.cookies()
    const refreshCookie = allCookies.find((c) => /refresh/i.test(c.name))
    expect(refreshCookie, 'refresh cookie should be set in the jar but invisible to JS').toBeDefined()
  })

  test('NFT-SEC-03 (row 07): refresh cookie attributes — Secure, HttpOnly, SameSite=Strict', async ({ page, context }) => {
    // Arrange + Act
    await page.goto('/login')
    const loginResp = await Promise.all([
      page.waitForResponse(
        (r) => r.url().includes('/api/admin/auth/login') && r.request().method() === 'POST',
      ),
      page.getByLabel(/email/i).fill(ALICE_EMAIL).then(() =>
        page.getByLabel(/password/i).fill(ALICE_PASSWORD),
      ).then(() => page.getByRole('button', { name: /sign in/i }).click()),
    ])

    // Inspect the Set-Cookie header from the login response.
    const setCookie = loginResp[0].headersArray()
      .filter((h) => h.name.toLowerCase() === 'set-cookie')
      .map((h) => h.value)
      .join(' ; ')
    expect(setCookie, 'Set-Cookie header missing').not.toEqual('')
    expect(setCookie).toMatch(/Secure/i)
    expect(setCookie).toMatch(/HttpOnly/i)
    expect(setCookie).toMatch(/SameSite\s*=\s*Strict/i)

    // Cross-check via the cookie jar (HttpOnly is visible to context.cookies()).
    const allCookies = await context.cookies()
    const refreshCookie = allCookies.find((c) => /refresh/i.test(c.name))
    expect(refreshCookie).toBeDefined()
    if (refreshCookie) {
      expect(refreshCookie.httpOnly).toBe(true)
      expect(refreshCookie.secure).toBe(true)
      expect(refreshCookie.sameSite).toBe('Strict')
    }
  })
})
