/**
 * Admin portal smoke + happy-path coverage.
 *
 * Covers `school_admin` (operational admin — the role with the largest
 * surface). The `systems_admin` block is included but skipped when the
 * demo seed doesn't include that user — `sysadmin@school.com` is
 * promised by Login.tsx's demoLogin() map but is not in
 * server/src/database/seeds/demo.js today. Fix the seed to un-skip.
 *
 * The smoke group walks every route the role can reach (per the App.tsx
 * route map). The happy-path group exercises one real interaction per
 * major module so a feature that renders but is dead on arrival still
 * shows up red.
 */
import { test, expect } from '@playwright/test';
import { loginAs, isRoleAvailable } from '../helpers/auth';
import { ROUTES_PER_ROLE, ROLE_LANDING } from '../helpers/nav';
import {
  captureConsoleErrors,
  expectNoConsoleErrors,
  expectPageMounted,
} from '../helpers/assertions';

const BASE = 'http://localhost:3000';

test.describe('Admin portal — school_admin smoke @smoke', () => {
  test.beforeEach(async ({ page }) => {
    await loginAs(page, 'school_admin');
  });

  for (const route of ROUTES_PER_ROLE.school_admin) {
    test(`mounts ${route}`, async ({ page }) => {
      const cap = captureConsoleErrors(page);
      try {
        await page.goto(`${BASE}${route}`);
        await expectPageMounted(page, route, [route.split('/').pop() || route, 'Africa Alert']);
      } finally {
        cap.dispose();
        expectNoConsoleErrors(cap.errors);
      }
    });
  }
});

test.describe('Admin portal — systems_admin smoke @smoke', () => {
  test.beforeAll(async () => {
    const ok = await isRoleAvailable('systems_admin');
    test.skip(!ok, 'systems_admin demo account not in seed — see server/src/database/seeds/demo.js');
  });

  test.beforeEach(async ({ page }) => {
    await loginAs(page, 'systems_admin');
  });

  for (const route of ROUTES_PER_ROLE.systems_admin) {
    test(`mounts ${route}`, async ({ page }) => {
      const cap = captureConsoleErrors(page);
      try {
        await page.goto(`${BASE}${route}`);
        await expectPageMounted(page, route, [route.split('/').pop() || route, 'Africa Alert']);
      } finally {
        cap.dispose();
        expectNoConsoleErrors(cap.errors);
      }
    });
  }
});

test.describe('Admin portal — happy path @flow', () => {
  test.beforeEach(async ({ page }) => {
    await loginAs(page, 'school_admin');
  });

  test('lands on the school admin dashboard after login', async ({ page }) => {
    await page.goto(`${BASE}/`);
    await expect(page).toHaveURL(new RegExp(`${ROLE_LANDING.school_admin.replace(/\//g, '\\/')}$`));
  });

  test('front-office: visitor log page is interactive', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/front-office/visitors`);
      await expect(page.locator('h1, h2').first()).toBeVisible();
      // The add button or the empty state should be reachable. Use a
      // generous locator — the label can be "Add", "New", or "Log visitor".
      const addOrEmpty = page
        .getByRole('button', { name: /add|new|log visitor/i })
        .or(page.getByText(/no visitors|empty/i));
      await expect(addOrEmpty.first()).toBeVisible({ timeout: 5_000 });
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });

  test('finance: expenses list page renders', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/finance/expenses`);
      await expect(page.locator('h1, h2').first()).toBeVisible();
      // The page either has a table of expenses or an empty state — both prove
      // the route mounted and the API call returned.
      const tableOrEmpty = page
        .locator('table')
        .or(page.getByText(/no expenses|no records|empty/i));
      await expect(tableOrEmpty.first()).toBeVisible({ timeout: 5_000 });
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });

  test('marks: bulk-marks page renders the upload surface', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/marks/bulk`);
      await expect(page.locator('h1, h2').first()).toBeVisible();
      // Should have either a file input or a CSV/paste input.
      const fileInput = page.locator('input[type="file"]');
      await expect(fileInput.first()).toBeAttached({ timeout: 5_000 });
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });

  test('notice board: admin can see the post surface', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/notice-board`);
      // Admin can post — look for a button, textarea, or a known label.
      const postSurface = page
        .getByRole('button', { name: /post|create|new|add/i })
        .or(page.locator('textarea'))
        .or(page.getByText(/notice|announcement/i));
      await expect(postSurface.first()).toBeVisible({ timeout: 5_000 });
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });

  test('library: catalog page is reachable', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/library`);
      await expect(page.locator('h1, h2').first()).toBeVisible();
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });

  // Exam-review workflow (added 2026-07-21). The page either shows
  // the queue table or the "queue clear" empty state — both prove the
  // /api/exams/groups/pending endpoint works and the page mounted.
  test('exams: review queue page renders', async ({ page }) => {
    const cap = captureConsoleErrors(page);
    try {
      await page.goto(`${BASE}/exams/review`);
      await expectPageMounted(page, '/exams/review', ['Exam Review', 'Queue']);
      const queueOrEmpty = page
        .locator('table')
        .or(page.getByText(/queue clear|no.*awaiting/i));
      await expect(queueOrEmpty.first()).toBeVisible({ timeout: 5_000 });
    } finally {
      cap.dispose();
      expectNoConsoleErrors(cap.errors);
    }
  });
});
