import { randomUUID } from 'node:crypto';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import type { GameServer } from '../src/server';
import { seedFixture, startServer, TestClient, type Fixture } from './helpers';

/** After a championship ends, phones go back to tonight's always-on game without rescanning. */
describe('championship → always-on handoff', () => {
  let fx: Fixture;
  let server: GameServer;
  let url: string;

  beforeAll(async () => {
    fx = await seedFixture();
    ({ server, url } = await startServer(fx));
    await (await server.rooms.getActor(fx.roomA.id))!.ensureAlwaysOn('2026-10-29');
  });

  afterAll(async () => {
    await server?.stop();
    await fx?.db.$disconnect();
  });

  it('moves a phone back into its always-on player when the show ends', async () => {
    const deviceId = randomUUID();
    const phone = new TestClient(url);
    await phone.connected();
    const join = (nickname: string) =>
      phone.emit<{ gameSessionId: string; playerSessionId: string }>('room.join', {
        venueCode: fx.venueA.joinCode,
        nickname,
        teamName: null,
        deviceId,
        idempotencyKey: randomUUID(),
      });

    const always = await join('Handoff Hal');
    expect(always.ok).toBe(true);
    if (!always.ok) return;

    const hostToken = await server.tokens.signHost({ sub: fx.hostA.id, venueId: fx.venueA.id, role: 'OWNER' }, Date.now());
    const host = new TestClient(url, { hostToken, eventId: fx.champA.id });
    await host.connected();
    expect((await host.emit('event.open', { eventId: fx.champA.id, idempotencyKey: randomUUID() })).ok).toBe(true);

    // The same phone takes the championship invite.
    const champ = await join('Handoff Hal');
    expect(champ.ok).toBe(true);
    if (!champ.ok) return;
    expect(champ.data!.gameSessionId).not.toBe(always.data!.gameSessionId);
    expect((await host.emit('event.start', { eventId: fx.champA.id, idempotencyKey: randomUUID() })).ok).toBe(true);

    const back = phone.next(
      (e) => e.type === 'session.token' && e.data?.gameSessionId === always.data!.gameSessionId,
      10_000,
      'token for the always-on session',
    );
    const backSnap = phone.next(
      (e) => e.type === 'room.state' && e.snapshot?.mySessionId === always.data!.gameSessionId && e.snapshot?.activeEventKind === 'ALWAYS_ON',
      10_000,
      'always-on snapshot',
    );
    expect((await host.emit('event.end', { eventId: fx.champA.id, idempotencyKey: randomUUID() })).ok).toBe(true);
    const token = await back;
    expect(token.data.playerSessionId).toBe(always.data!.playerSessionId);
    const snap = await backSnap;
    expect(snap.snapshot.mySessionActive).toBe(true);
    expect(snap.snapshot.activeEventKind).toBe('ALWAYS_ON');

    // The phone is a working always-on player again.
    const state = await phone.emit('state.request', { lastSeq: 0 });
    expect(state.ok && (state.data as { me: { playerId: string } }).me.playerId).toBe(always.data!.playerSessionId);
    host.close();
    phone.close();
  }, 30_000);
});
