const WebSocket = require('ws');
const request = require('supertest');
const { app, server } = require('../../backend/server');
const { getAuthToken, getAuthHeader, cleanDb, seedGame, seedSession } = require('../helpers/test-utils');

function connectWs() {
  return new WebSocket(`ws://localhost:${server.address().port}/api/sessions/live`);
}

function waitForMessage(ws, type, timeoutMs = 3000) {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => reject(new Error(`Timeout waiting for ${type}`)), timeoutMs);
    ws.on('message', function handler(data) {
      const msg = JSON.parse(data.toString());
      if (msg.type === type) {
        clearTimeout(timeout);
        ws.removeListener('message', handler);
        resolve(msg);
      }
    });
  });
}

function authenticateAndSubscribe(ws, sessionId) {
  return new Promise(async (resolve, reject) => {
    try {
      ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
      await waitForMessage(ws, 'auth_success');
      ws.send(JSON.stringify({ type: 'subscribe', sessionId }));
      await waitForMessage(ws, 'subscribed');
      resolve();
    } catch (err) {
      reject(err);
    }
  });
}

beforeAll((done) => {
  server.listen(0, () => done());
});

afterAll((done) => {
  server.close(done);
});

describe('WebSocket events (regression)', () => {
  beforeEach(() => {
    cleanDb();
  });

  test('auth flow: auth -> auth_success', (done) => {
    const ws = connectWs();
    ws.on('open', () => {
      ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
    });
    ws.on('message', (data) => {
      const msg = JSON.parse(data.toString());
      if (msg.type === 'auth_success') {
        ws.close();
        done();
      }
    });
  });

  test('subscribe/unsubscribe flow', async () => {
    const session = seedSession({ is_active: 1 });
    const ws = connectWs();
    await new Promise((resolve) => ws.on('open', resolve));

    ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
    await waitForMessage(ws, 'auth_success');

    ws.send(JSON.stringify({ type: 'subscribe', sessionId: session.id }));
    const subMsg = await waitForMessage(ws, 'subscribed');
    expect(subMsg.sessionId).toBe(session.id);

    ws.send(JSON.stringify({ type: 'unsubscribe', sessionId: session.id }));
    const unsubMsg = await waitForMessage(ws, 'unsubscribed');
    expect(unsubMsg.sessionId).toBe(session.id);

    ws.close();
  });

  test('session.started broadcasts to all authenticated clients', async () => {
    const ws = connectWs();
    await new Promise((resolve) => ws.on('open', resolve));

    ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
    await waitForMessage(ws, 'auth_success');

    const eventPromise = waitForMessage(ws, 'session.started');

    await request(app)
      .post('/api/sessions')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({ notes: 'Test session' });

    const event = await eventPromise;
    expect(event.data.session).toEqual(
      expect.objectContaining({
        is_active: 1,
        notes: 'Test session',
      })
    );

    ws.close();
  });

  test('session.ended broadcasts to session subscribers', async () => {
    const session = seedSession({ is_active: 1 });
    const ws = connectWs();
    await new Promise((resolve) => ws.on('open', resolve));

    await authenticateAndSubscribe(ws, session.id);

    const eventPromise = waitForMessage(ws, 'session.ended');

    await request(app)
      .post(`/api/sessions/${session.id}/close`)
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json');

    const event = await eventPromise;
    expect(event.data.session.id).toBe(session.id);
    expect(event.data.session.is_active).toBe(0);

    ws.close();
  });

  test('game.added broadcasts to session subscribers', async () => {
    const game = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' });
    const session = seedSession({ is_active: 1 });
    const ws = connectWs();
    await new Promise((resolve) => ws.on('open', resolve));

    await authenticateAndSubscribe(ws, session.id);

    const eventPromise = waitForMessage(ws, 'game.added');

    await request(app)
      .post(`/api/sessions/${session.id}/games`)
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({ game_id: game.id });

    const event = await eventPromise;
    expect(event.data.game.title).toBe('Quiplash 3');
    expect(event.data.session.id).toBe(session.id);

    ws.close();
  });
});
