/**
 * calendar.spec.ts — TEST-01 + TEST-02
 *
 * Route-specific state assertions for /calendar (UI-SPEC Rules 4/5):
 *   - Populated state: Schedule-X grid visible, EmptyState absent
 *   - Error state: 'Couldn't load events' heading + Retry button ≥44px + no overflow
 *   - Auth-bypass precondition: authed content reached via DEV_AUTH_BYPASS (no OIDC mock)
 *   - SW-block precondition: navigator.serviceWorker.controller is null (no controlling SW)
 *
 * Requires the dev stack running with DEV_AUTH_BYPASS=true (see e2e/README.md).
 * global-setup seeds 'Seeded Test Event' on calendar_id=10 for user_id=1.
 *
 * Runs on all three device profiles automatically (playwright.config.ts matrix):
 *   iphone:  iPhone 14 / WebKit / 390×844
 *   pixel:   Pixel 7 / Chromium / 412×915
 *   desktop: Desktop Chrome / Chromium / 1280×720 (all tests pass unchanged)
 *
 * Run:
 *   pnpm --filter @familysync/pwa test:e2e
 *   pnpm --filter @familysync/pwa exec playwright test --project=pixel calendar.spec.ts
 */
import { test, expect } from '@playwright/test';

// ── TEST-02 preconditions: DEV_AUTH_BYPASS + no SW controller ─────────────────

test.describe('TEST-02 preconditions — auth bypass and SW block', () => {
  test('DEV_AUTH_BYPASS reached authed PWA without OIDC mock', async ({ page }) => {
    await page.goto('/calendar');

    // Wait for the authed content to appear — DEV_AUTH_BYPASS should resolve immediately
    // without Authelia redirect. The BottomTabBar nav landmark is only rendered after auth.
    const nav = page.getByRole('navigation', { name: 'Main navigation' });
    await expect(nav).toBeVisible();

    // Confirm we are NOT on an external auth host (Authelia login page would redirect the URL)
    const url = new URL(page.url());
    expect(
      url.hostname,
      `Expected to remain on localhost or 127.0.0.1, got: ${url.hostname}`,
    ).toMatch(/^(localhost|127\.0\.0\.1)$/);
  });

  test('no service-worker registration (serviceWorkers: block enforced)', async ({ page }) => {
    await page.goto('/calendar');

    // serviceWorkers: 'block' in playwright.config.ts prevents SW registration.
    //
    // WR-07: asserting `navigator.serviceWorker.controller === null` is near-vacuous —
    //  (a) on WebKit over plain http://localhost, `serviceWorker` is often *absent* from
    //      navigator (secure-context strictness), so the old guard returned null and the
    //      assertion passed without ever proving the block worked; and
    //  (b) `controller` is null on a first uncontrolled load even when SW *is* available,
    //      regardless of the block setting.
    // Instead probe getRegistration() — when SW is available and `block` is in effect, no
    // registration exists, so it resolves to undefined. Where `serviceWorker` is absent
    // entirely (WebKit/http), skip rather than let an unavailable API masquerade as a pass.
    const swAvailable = await page.evaluate(
      () => typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
    );
    test.skip(
      !swAvailable,
      'navigator.serviceWorker is unavailable in this context (e.g. WebKit over http://localhost) — block is unobservable here',
    );

    const registration = await page.evaluate(() => navigator.serviceWorker.getRegistration());
    expect(
      registration,
      'No service worker should be registered (serviceWorkers:block enforced)',
    ).toBeUndefined();
  });
});

// ── Rule 5: Populated state ───────────────────────────────────────────────────

test.describe('Rule 5 — populated calendar state', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/calendar');
    // Wait for auth and Schedule-X to render before asserting
    await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();
  });

  test('Schedule-X calendar grid is visible after seeding', async ({ page }) => {
    // The Schedule-X React adapter emits a div.sx-react-calendar-wrapper.
    // Prefer a stable locator: the class name is documented in apps/pwa/src/styles/index.css.
    // No semantic role exists for the widget wrapper, so CSS class is the documented fallback.
    // NOTE: the wrapper renders on any successful auth — this proves the grid mounts, NOT that
    // the seed reached the UI. The DB→UI proof is the separate "seeded event is rendered" test.
    const calendarGrid = page.locator('.sx-react-calendar-wrapper');
    await expect(calendarGrid).toBeVisible();
  });

  test('seeded event "Seeded Test Event" is rendered in the grid (DB→UI proof)', async ({
    page,
  }) => {
    // The one assertion that actually proves the seeded row flows DB → API → query → grid.
    // global-setup seeds a timed event titled 'Seeded Test Event' (noon today) on calendar 10.
    // Schedule-X renders the event with its title text inside the grid. If the seed broke, the
    // /api/events join regressed, or hydration dropped events, THIS fails (unlike a wrapper /
    // dead-EmptyState check, which would stay green). Deep-review BL-01.
    await expect(page.getByText('Seeded Test Event').first()).toBeVisible();
  });

  test('no horizontal overflow on populated /calendar (Rule 2)', async ({ page }) => {
    const overflow = await page.evaluate(() => ({
      scrollWidth: document.documentElement.scrollWidth,
      clientWidth: document.documentElement.clientWidth,
    }));
    expect(
      overflow.scrollWidth,
      `scrollWidth (${overflow.scrollWidth}) must be ≤ clientWidth (${overflow.clientWidth}) on populated /calendar`,
    ).toBeLessThanOrEqual(overflow.clientWidth);
  });
});

// ── Rule 5: Error state ───────────────────────────────────────────────────────

test.describe('Rule 5 — calendar error state (API mocked to 500)', () => {
  test('error heading + Retry button visible when /api/events returns 500', async ({ page }) => {
    // Register route BEFORE page.goto — the intercept must be in place before navigation
    // so the very first events request is caught (Pattern 5).
    await page.route('/api/events*', (route) =>
      route.fulfill({ status: 500, body: JSON.stringify({ error: 'simulated' }) }),
    );

    await page.goto('/calendar');

    // Wait for auth (DEV_AUTH_BYPASS)
    await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();

    // eventsQuery has retry:2 so Playwright may need to wait for all retries before
    // the error branch renders. Use default Playwright timeout.
    const errorHeading = page.getByRole('heading', { name: "Couldn't load events" });
    await expect(errorHeading).toBeVisible();

    const retryBtn = page.getByRole('button', { name: 'Retry' });
    await expect(retryBtn).toBeVisible();
    // No manual unroute (WR-05): Playwright gives each test a fresh page/context, so route
    // handlers do not leak across tests. A trailing unroute also never runs if an `expect`
    // above throws — it was misleading "cleanup" that guaranteed nothing.
  });

  test('Retry button meets 44px touch-target minimum in error state (Rule 1)', async ({ page }) => {
    await page.route('/api/events*', (route) =>
      route.fulfill({ status: 500, body: JSON.stringify({ error: 'simulated' }) }),
    );

    await page.goto('/calendar');
    await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();

    const retryBtn = page.getByRole('button', { name: 'Retry' });
    await expect(retryBtn).toBeVisible();

    const box = await retryBtn.boundingBox();
    expect(box, 'Retry button bounding box must not be null').not.toBeNull();
    expect(box!.height, 'Retry button height must be ≥ 44px (Rule 1)').toBeGreaterThanOrEqual(44);
    // No manual unroute (WR-05): per-test context isolation handles route cleanup.
  });

  test('no horizontal overflow in error state (Rule 2)', async ({ page }) => {
    await page.route('/api/events*', (route) =>
      route.fulfill({ status: 500, body: JSON.stringify({ error: 'simulated' }) }),
    );

    await page.goto('/calendar');
    await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();

    // Wait for error heading to confirm the error branch has rendered
    await expect(page.getByRole('heading', { name: "Couldn't load events" })).toBeVisible();

    const overflow = await page.evaluate(() => ({
      scrollWidth: document.documentElement.scrollWidth,
      clientWidth: document.documentElement.clientWidth,
    }));
    expect(
      overflow.scrollWidth,
      `scrollWidth (${overflow.scrollWidth}) must be ≤ clientWidth (${overflow.clientWidth}) in error state`,
    ).toBeLessThanOrEqual(overflow.clientWidth);
    // No manual unroute (WR-05): per-test context isolation handles route cleanup.
  });
});
