/**
 * US-5 — UI respects DESIGN.md tokens, layout doesn't break at 1280×720,
 * and loading/error/empty states are wired.
 *
 * Pixel-perfect fidelity is NOT enforced — instead we assert that the
 * tokenised classes from tailwind.config.ts are present and that the canvas
 * has no horizontal overflow at the design viewport.
 */
import { test, expect } from '@playwright/test';
import {
  adminToken,
  deleteAllEngagements,
  deleteUserByUsername,
  ensureUser,
  login,
} from '../fixtures/api';
import { seedTokenInStorage } from '../fixtures/auth';

const REDTEAM_USER = 'us5-redteam';
const PASS = 'us5-passw0rd';

test.describe('US-5 — DESIGN.md fidelity, responsive, states', () => {
  let redteamToken: string;

  test.beforeAll(async () => {
    await ensureUser(REDTEAM_USER, PASS, 'redteam');
    redteamToken = (await login(REDTEAM_USER, PASS)).token;
  });

  test.afterAll(async () => {
    try {
      const tok = await adminToken();
      await deleteAllEngagements(tok);
      await deleteUserByUsername(tok, REDTEAM_USER);
    } catch {
      /* noop */
    }
  });

  test('AC-5.1 — DESIGN.md tokens applied (Inter font, brand palette, named utilities)', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto('/engagements');

    // Body font must resolve to a stack including "Inter".
    const fontFamily = await page.evaluate(
      () => window.getComputedStyle(document.body).fontFamily,
    );
    expect(fontFamily.toLowerCase()).toMatch(/inter/);

    // Tailwind-compiled DESIGN palette: the primary chevron at the top-left of
    // the nav uses `bg-primary` → rendered as rgb(2, 74, 216).
    const chevronBg = await page
      .locator('header a[aria-label="Mimic home"] span[aria-hidden]')
      .first()
      .evaluate((el) => window.getComputedStyle(el).backgroundColor);
    expect(chevronBg.replace(/\s/g, '')).toBe('rgb(2,74,216)');

    // Sprint 4: topbar utility-strip uses `bg-slab` (#111827 → rgb(17,24,39)).
    const utilityBg = await page
      .locator('div.bg-slab.text-slab-text')
      .first()
      .evaluate((el) => window.getComputedStyle(el).backgroundColor);
    expect(utilityBg.replace(/\s/g, '')).toBe('rgb(17,24,39)');

    // Spot-check a few semantic class names live in the DOM (proves tokens are
    // wired through tailwind.config.ts and not ad-hoc hex values).
    await expect(page.locator('.btn-primary, .card-product, .max-w-page').first()).toBeVisible();
  });

  test('AC-5.2 — no horizontal overflow at 1280×720 across key pages', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.setViewportSize({ width: 1280, height: 720 });

    const routes = ['/engagements', '/engagements/new'];
    for (const route of routes) {
      await page.goto(route);
      const overflow = await page.evaluate(() => ({
        scrollWidth: document.documentElement.scrollWidth,
        clientWidth: document.documentElement.clientWidth,
      }));
      expect(
        overflow.scrollWidth,
        `${route} should not horizontally overflow at 1280px`,
      ).toBeLessThanOrEqual(overflow.clientWidth + 1);
    }
  });

  test('AC-5.3a — empty state renders when engagements list is empty', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    const tok = await adminToken();
    await deleteAllEngagements(tok);
    await page.goto('/engagements');
    await expect(page.getByRole('heading', { name: /no engagements yet/i })).toBeVisible();
  });

  test('AC-5.3b — loading state renders while engagements list is in-flight', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);

    // Stall the list endpoint so the LoadingState becomes visible.
    const handler = async (route: import('@playwright/test').Route) => {
      const url = route.request().url();
      if (/\/api\/engagements(\?.*)?$/.test(url)) {
        await new Promise((r) => setTimeout(r, 1500));
      }
      try {
        await route.continue();
      } catch {
        /* route may have been torn down by the time we resume */
      }
    };
    await page.route('**/api/engagements**', handler);

    const navPromise = page.goto('/engagements');
    await expect(page.getByText(/loading engagements/i)).toBeVisible({ timeout: 3_000 });
    await navPromise;
    await page.unroute('**/api/engagements**', handler);
  });

  test('AC-5.3c — error state renders when the engagements list returns 500', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);

    const handler = async (route: import('@playwright/test').Route) => {
      const url = route.request().url();
      if (/\/api\/engagements(\?.*)?$/.test(url)) {
        await route.fulfill({
          status: 500,
          contentType: 'application/json',
          body: JSON.stringify({ error: 'Forced failure' }),
        });
        return;
      }
      try {
        await route.continue();
      } catch {
        /* route teardown race — harmless */
      }
    };
    await page.route('**/api/engagements**', handler);

    await page.goto('/engagements');
    await expect(page.getByTestId('error-state')).toBeVisible({ timeout: 8_000 });
    await expect(page.getByTestId('error-state')).toContainText(/forced failure/i);
    await expect(page.getByRole('button', { name: /retry/i })).toBeVisible();
    await page.unroute('**/api/engagements**', handler);
  });
});
