/**
 * Central `test.extend` for EventSnap E2E tests. Specs import `test` from here
 * (not `@playwright/test` directly) so they get the shared fixtures: API
 * client, DB helper, fresh admin token, and convenience factories.
 *
 * Every test runs against a freshly truncated database (via the `truncate`
 * auto-fixture). Because truncate wipes the `session` table, any admin JWT
 * obtained before truncate becomes invalid afterwards — so the
 * `truncate` fixture always does its own admin login, and the `adminToken`
 * fixture re-logs-in per test instead of caching. bcrypt cost 4 → ~10 ms,
 * negligible against the ~1 s per-test setup overhead.
 */
import { test as base, expect, type Page } from '@playwright/test';
import { ApiClient } from './api-client';
import { db } from './db';

type GuestHandle = {
  jwt: string;
  pin: string;
  userId: string;
  displayName: string;
};

type Fixtures = {
  api: ApiClient;
  db: typeof db;
  adminToken: string;
  truncate: void;
  guest: (displayName?: string) => Promise<GuestHandle>;
  host: GuestHandle;
  /** Apply an existing guest's JWT + PIN to the page's localStorage and reload. */
  signIn: (page: Page, handle: GuestHandle) => Promise<void>;
};

export const test = base.extend<Fixtures>({
  api: async ({}, use) => {
    await use(new ApiClient());
  },

  db: async ({}, use) => {
    await use(db);
  },

  // Auto-fixture: runs before every test, truncates the DB so each test starts
  // clean. Acquires its OWN admin token because the previous test's truncate
  // wiped the session row that backs any cached token.
  truncate: [
    async ({ api }, use) => {
      const token = await api.adminLogin();
      await api.truncate(token);
      await use();
    },
    { auto: true, scope: 'test' },
  ],

  // Fresh admin login per test that asks for it. Comes AFTER the truncate
  // auto-fixture has run (truncate doesn't depend on adminToken, so the
  // dependency-free truncate runs first; this fixture then logs in on a
  // freshly reset DB).
  adminToken: async ({ api }, use) => {
    const token = await api.adminLogin();
    await use(token);
  },

  guest: async ({ api }, use) => {
    let counter = 0;
    const factory = async (displayName?: string): Promise<GuestHandle> => {
      const name = displayName ?? `Gast${++counter}_${Math.random().toString(36).slice(2, 6)}`;
      const res = await api.join(name);
      return { jwt: res.jwt, pin: res.pin, userId: res.user_id, displayName: name };
    };
    await use(factory);
  },

  host: async ({ api, guest, adminToken }, use) => {
    const h = await guest('TestHost');
    await api.setRole(adminToken, h.userId, 'host');
    // Role is encoded in the JWT — re-recover to get a fresh token with role=host.
    const { body } = await api.recover(h.displayName, h.pin);
    await use({ ...h, jwt: body.jwt });
  },

  signIn: async ({}, use) => {
    const fn = async (page: Page, handle: GuestHandle) => {
      // Visit any in-app URL first so localStorage is scoped to the right origin.
      await page.goto('/');
      await page.evaluate(({ jwt, pin, userId, displayName }) => {
        localStorage.setItem('eventsnap_jwt', jwt);
        localStorage.setItem('eventsnap_pin', pin);
        localStorage.setItem('eventsnap_user_id', userId);
        localStorage.setItem('eventsnap_display_name', displayName);
        localStorage.setItem('eventsnap_guide_seen', 'true');
      }, handle);
      await page.goto('/feed');
    };
    await use(fn);
  },
});

export { expect };
