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

/** Restarts around championships: lobbies come back joinable; ended shows stay ended. */
describe('championship recovery across restarts', () => {
  let fx: Fixture;
  let server: GameServer;
  let url: string;

  beforeAll(async () => {
    fx = await seedFixture();
    ({ server, url } = await startServer(fx));
  });

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

  async function host(eventId: string) {
    const hostToken = await server.tokens.signHost({ sub: fx.hostB.id, venueId: fx.venueB.id, role: 'OWNER' }, Date.now());
    const c = new TestClient(url, { hostToken, eventId });
    await c.connected();
    return c;
  }

  async function restart() {
    await server.stop();
    ({ server, url } = await startServer(fx));
  }

  it('restores an open lobby as the active, joinable session', async () => {
    const h = await host(fx.champB.id);
    expect((await h.emit('event.open', { eventId: fx.champB.id, idempotencyKey: randomUUID() })).ok).toBe(true);
    const first = await joinAs(url, fx.venueB.joinCode, 'Early Bird');
    expect(first.res.ok).toBe(true);
    if (!first.res.ok) return;
    h.close();
    first.client.close();
    await (await server.rooms.getActor(fx.roomB.id))!.drain();

    await restart();
    const actor = (await server.rooms.getActor(fx.roomB.id))!;
    expect(actor.activeSession()?.event.id).toBe(fx.champB.id);
    expect(actor.activeSession()?.state.status).toBe('WAITING');

    // The early bird reconnects with their token; a newcomer can still join the lobby.
    const back = new TestClient(url, { tokens: [first.res.data!.token] });
    await back.connected();
    const snap = await back.waitFor((e) => e.type === 'room.state' && !!e.snapshot, 5_000, 'snapshot');
    expect(snap.snapshot.me.nickname).toBe('Early Bird');
    expect(snap.snapshot.mySessionActive).toBe(true);
    const late = await joinAs(url, fx.venueB.joinCode, 'Late Comer');
    expect(late.res.ok).toBe(true);
    if (late.res.ok) expect(late.res.data!.gameSessionId).toBe(first.res.data!.gameSessionId);
    back.close();
    late.client.close();
  }, 60_000);

  it('an ended show stays ended after a restart, and the next show can open', async () => {
    const h = await host(fx.champB.id);
    expect((await h.emit('event.start', { eventId: fx.champB.id, idempotencyKey: randomUUID() })).ok).toBe(true);
    expect((await h.emit('event.end', { eventId: fx.champB.id, idempotencyKey: randomUUID() })).ok).toBe(true);
    h.close();
    await (await server.rooms.getActor(fx.roomB.id))!.drain();
    const row = await fx.db.gameSession.findUnique({ where: { eventId: fx.champB.id } });
    expect(row?.status).toBe('COMPLETED');

    await restart();
    const actor = (await server.rooms.getActor(fx.roomB.id))!;
    expect(actor.sessionForEvent(fx.champB.id)).toBeNull();

    const next = await fx.db.event.create({
      data: {
        venueId: fx.venueB.id,
        roomId: fx.roomB.id,
        kind: 'CHAMPIONSHIP',
        name: 'Next Show',
        scheduledAt: new Date(Date.now() + 3_600_000),
        config: { plan: [{ kind: 'RUSH', packKey: fx.packKey, label: 'The Rush', segment: 0 }], mergeSolos: false },
      },
    });
    const h2 = await host(next.id);
    const opened = await h2.emit('event.open', { eventId: next.id, idempotencyKey: randomUUID() });
    expect(opened).toEqual({ ok: true });
    // Re-opening the finished show is refused cleanly.
    const reopen = await h2.emit('event.open', { eventId: fx.champB.id, idempotencyKey: randomUUID() });
    expect(reopen.ok).toBe(false);
    h2.close();
  }, 60_000);
});
