import { expect, type APIResponse, type Page, test } from '@playwright/test';

/**
 * Cross-tenant isolation e2e (Workstream 8 / Workstream 2). Proves a non-global
 * user of one organization cannot read, list, update, or delete a record owned
 * by a second organization. Uses the seeded second-tenant owner (`owner2`, the
 * `Rival Academy` org) and the primary-tenant owner (`owner`, `Demo Academy`).
 *
 * Requires the backend running with the database migrated + seeded, and the
 * seed passwords in the environment.
 */
const USER_PASSWORD = 'flatlogicUser123!';

const BACKEND_API_URL =
  process.env.VITE_BACKEND_API_URL || 'http://localhost:8080/api';

const PRIMARY_OWNER_EMAIL = 'owner@flatlogic.com';
const SECONDARY_OWNER_EMAIL = 'owner2@flatlogic.com';

const ACADEMIC_YEARS = `${BACKEND_API_URL}/academic_years`;

interface AcademicYearRow {
  readonly id: string;
  readonly name: string | null;
}

async function logout(page: Page): Promise<void> {
  await page.request.post(`${BACKEND_API_URL}/auth/signout`);
  await page.context().clearCookies();
}

async function login(page: Page, email: string, password: string): Promise<void> {
  await logout(page);
  await page.goto('/login');
  await page.getByPlaceholder('you@school.edu').fill(email);
  await page.getByPlaceholder('Enter your password').fill(password);
  await page.getByRole('button', { name: 'Sign In', exact: true }).click();
  await page.waitForURL((url) => !url.pathname.startsWith('/login'), {
    timeout: 10_000,
  });
}

async function listRows(page: Page): Promise<AcademicYearRow[]> {
  const res = await page.request.get(ACADEMIC_YEARS);
  expect(res.status()).toBe(200);
  const body = (await res.json()) as { rows?: AcademicYearRow[] };
  return body.rows ?? [];
}

async function findByName(
  page: Page,
  name: string,
): Promise<AcademicYearRow | undefined> {
  return (await listRows(page)).find((row) => row.name === name);
}

test.describe('Cross-tenant isolation', () => {
  test('a tenant cannot read, list, mutate, or delete another tenant record', async ({
    page,
  }) => {
    const rivalName = `Rival Year ${Date.now()}`;

    // 1. owner2 (Rival Academy) creates a record in its own tenant.
    await login(page, SECONDARY_OWNER_EMAIL, USER_PASSWORD);
    const created = await page.request.post(ACADEMIC_YEARS, {
      data: { data: { name: rivalName } },
    });
    expect(created.status()).toBe(200);

    const rival = await findByName(page, rivalName);
    expect(rival, 'owner2 should see its own record').toBeTruthy();
    const rivalId = rival!.id;

    // 2. owner (Demo Academy) must not see it in any way.
    await login(page, PRIMARY_OWNER_EMAIL, USER_PASSWORD);

    // 2a. List isolation.
    const primaryRows = await listRows(page);
    expect(primaryRows.some((row) => row.id === rivalId)).toBe(false);

    // 2b. Read-by-id isolation: tenant-scoped read returns no record.
    const readRes = await page.request.get(`${ACADEMIC_YEARS}/${rivalId}`);
    const readBody = await readRes.text();
    expect(readBody).not.toContain(rivalName);

    // 2c. Update isolation: a tenant-scoped update finds nothing → error.
    const updateRes: APIResponse = await page.request.put(
      `${ACADEMIC_YEARS}/${rivalId}`,
      { data: { id: rivalId, data: { name: 'Hijacked' } } },
    );
    expect(updateRes.ok()).toBe(false);

    // 2d. Delete isolation: attempt to delete the rival record.
    await page.request.delete(`${ACADEMIC_YEARS}/${rivalId}`);

    // 3. owner2 confirms its record still exists and is unchanged.
    await login(page, SECONDARY_OWNER_EMAIL, USER_PASSWORD);
    const stillThere = await findByName(page, rivalName);
    expect(
      stillThere,
      'the rival record must survive the cross-tenant update/delete attempts',
    ).toBeTruthy();
  });
});
