/**
 * E2E tests for the 3 deployment-readiness fixes
 * (commit 039bb37 on fix/deployment-readiness-blockers).
 *
 * Each test exercises the running server on :3001 (and the Vite client
 * on :3000 when Playwright `page` is used) and verifies the runtime
 * behavior the fix was meant to enforce.
 */
import { test, expect, request as pwRequest } from '@playwright/test';

const API = 'http://localhost:3001';

const ADMIN = { email: 'admin@school.com', password: 'admin123' };
const PARENT = { email: 'parent@school.com', password: 'parent123' };
const STUDENT = { email: 'student@school.com', password: 'student123' };
const TEACHER = { email: 'teacher@school.com', password: 'teacher123' };

async function loginAs(creds: { email: string; password: string }) {
  const ctx = await pwRequest.newContext({ baseURL: API });
  const res = await ctx.post('/api/auth/login', { data: creds });
  expect(res.status(), `login as ${creds.email}`).toBe(200);
  const body = await res.json();
  return { ctx, token: body.token as string, user: body.user };
}

test.describe('fix 1: password_hash no longer in login response', () => {
  test('admin login response has no password_hash field', async () => {
    const { ctx, user } = await loginAs(ADMIN);
    expect(user).not.toHaveProperty('password_hash');
    await ctx.dispose();
  });

  test('parent login response has no password_hash field', async () => {
    const { ctx, user } = await loginAs(PARENT);
    expect(user).not.toHaveProperty('password_hash');
    await ctx.dispose();
  });

  test('teacher login response has no password_hash field', async () => {
    const { ctx, user } = await loginAs(TEACHER);
    expect(user).not.toHaveProperty('password_hash');
    await ctx.dispose();
  });

  test('student login response has no password_hash field', async () => {
    const { ctx, user } = await loginAs(STUDENT);
    expect(user).not.toHaveProperty('password_hash');
    await ctx.dispose();
  });

  test('caller can still fetch their own hash via /api/auth/me/credentials', async () => {
    const { ctx, token, user } = await loginAs(ADMIN);
    const res = await ctx.get('/api/auth/me/credentials', {
      headers: { Authorization: `Bearer ${token}` },
    });
    expect(res.status()).toBe(200);
    const body = await res.json();
    expect(body).toHaveProperty('password_hash');
    expect(body.password_hash).toMatch(/^\$2[aby]\$/);     // bcrypt hash shape
    expect(body.email).toBe(user.email);
    await ctx.dispose();
  });

  test('/api/auth/me/credentials rejects unauthenticated callers', async () => {
    const ctx = await pwRequest.newContext({ baseURL: API });
    const res = await ctx.get('/api/auth/me/credentials');
    expect(res.status()).toBe(401);
    await ctx.dispose();
  });
});

test.describe('fix 2: /api/messages/contacts is role-scoped', () => {
  test('parent sees only staff + their children\'s teachers (no other students)', async () => {
    const { ctx, token } = await loginAs(PARENT);
    const res = await ctx.get('/api/messages/contacts', {
      headers: { Authorization: `Bearer ${token}` },
    });
    const contacts = await res.json();

    expect(Array.isArray(contacts)).toBe(true);

    // No 'student' or 'parent' rows should appear for a parent's contact list.
    const studentRows = contacts.filter((c: any) => c.role === 'student');
    const parentRows = contacts.filter((c: any) => c.role === 'parent');
    expect(studentRows, 'parent must not see other students').toHaveLength(0);
    expect(parentRows, 'parent must not see other parents').toHaveLength(0);

    // The result should be small (was 100 before the fix; now ~12).
    expect(contacts.length).toBeLessThan(40);
    expect(contacts.length).toBeGreaterThan(0);
    await ctx.dispose();
  });

  test('student sees only staff + their class teachers (no other students/parents)', async () => {
    const { ctx, token } = await loginAs(STUDENT);
    const res = await ctx.get('/api/messages/contacts', {
      headers: { Authorization: `Bearer ${token}` },
    });
    const contacts = await res.json();
    expect(contacts.every((c: any) => c.role !== 'student')).toBe(true);
    expect(contacts.every((c: any) => c.role !== 'parent')).toBe(true);
    expect(contacts.length).toBeLessThan(40);
    await ctx.dispose();
  });

  test('teacher sees students in their classes + colleagues + staff', async () => {
    const { ctx, token } = await loginAs(TEACHER);
    const res = await ctx.get('/api/messages/contacts', {
      headers: { Authorization: `Bearer ${token}` },
    });
    const contacts = await res.json();
    // Teachers may see students in their classes but never other parents.
    expect(contacts.every((c: any) => c.role !== 'parent')).toBe(true);
    await ctx.dispose();
  });

  test('admin sees staff + teachers but no parent PII', async () => {
    const { ctx, token } = await loginAs(ADMIN);
    const res = await ctx.get('/api/messages/contacts', {
      headers: { Authorization: `Bearer ${token}` },
    });
    const contacts = await res.json();
    expect(contacts.every((c: any) => c.role !== 'parent')).toBe(true);
    await ctx.dispose();
  });

  test('contacts endpoint is authenticated', async () => {
    const ctx = await pwRequest.newContext({ baseURL: API });
    const res = await ctx.get('/api/messages/contacts');
    expect(res.status()).toBe(401);
    await ctx.dispose();
  });
});

test.describe('fix 3: client builds cleanly (ExamEditor no longer has duplicate symbol)', () => {
  // The strongest proof for fix 3 is `npm run build` succeeding (which we
  // verify outside this test). Here we prove the side-effect: the React
  // app shell loads, and navigating to the teacher-only /exams/manage
  // route does NOT produce a vite/react runtime error overlay.
  test('client SPA shell loads on http://localhost:3000', async () => {
    const ctx = await pwRequest.newContext({ baseURL: 'http://localhost:3000' });
    const res = await ctx.get('/');
    expect(res.status()).toBe(200);
    const body = await res.text();
    expect(body).toContain('<div id="root">');
    await ctx.dispose();
  });

  test('teacher can reach the /exams/manage page without runtime errors', async ({ page }) => {
    // Login as teacher through the UI
    await page.goto('/login');
    await page.fill('input[type="email"]', TEACHER.email);
    await page.fill('input[type="password"]', TEACHER.password);
    await page.click('button[type="submit"]');

    // Wait for redirect away from /login
    await page.waitForURL((url) => !url.pathname.startsWith('/login'), { timeout: 15_000 });

    // Navigate to the exam registry. The "Module Under Construction" stub
    // now renders without a duplicate-symbol runtime crash because the
    // build is clean.
    await page.goto('/exams/manage');
    // Wait for the page to settle (catch any runtime error overlay)
    await page.waitForLoadState('networkidle', { timeout: 10_000 });
    // No React error overlay
    const errorOverlay = await page.locator('vite-error-overlay').count();
    expect(errorOverlay).toBe(0);
  });
});

test.describe('general: full stack is reachable', () => {
  test('GET /api/dashboard/stats with admin returns students > 0', async () => {
    const { ctx, token } = await loginAs(ADMIN);
    const res = await ctx.get('/api/dashboard/stats', {
      headers: { Authorization: `Bearer ${token}` },
    });
    expect(res.status()).toBe(200);
    const body = await res.json();
    expect(body.stats.totalStudents).toBeGreaterThan(0);
    await ctx.dispose();
  });
});
