const WebSocket = require('ws');
const request = require('supertest');
const { app, server } = require('../../backend/server');
const { getAuthToken, getAuthHeader, cleanDb, seedGame, seedSession, seedSessionGame } = 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);
      }
    });
  });
}

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

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

describe('vote.received WebSocket event', () => {
  const baseTime = '2026-03-15T20:00:00.000Z';

  beforeEach(() => {
    cleanDb();
  });

  test('broadcasts vote.received to session subscribers on live vote', async () => {
    const game = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' });
    const session = seedSession({ is_active: 1 });
    seedSessionGame(session.id, game.id, { status: 'playing', played_at: baseTime });

    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 }));
    await waitForMessage(ws, 'subscribed');

    const eventPromise = waitForMessage(ws, 'vote.received');

    await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: '2026-03-15T20:05:00.000Z',
      });

    const event = await eventPromise;

    expect(event.data.sessionId).toBe(session.id);
    expect(event.data.game).toEqual({
      id: game.id,
      title: 'Quiplash 3',
      pack_name: 'Party Pack 7',
    });
    expect(event.data.vote).toEqual({
      username: 'viewer1',
      type: 'up',
      timestamp: '2026-03-15T20:05:00.000Z',
    });
    expect(event.data.totals).toEqual({
      upvotes: 1,
      downvotes: 0,
      popularity_score: 1,
    });

    ws.close();
  });

  test('does not broadcast on duplicate vote (409)', async () => {
    const game = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' });
    const session = seedSession({ is_active: 1 });
    seedSessionGame(session.id, game.id, { status: 'playing', played_at: baseTime });

    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 }));
    await waitForMessage(ws, 'subscribed');

    // First vote succeeds - set up listener before POST to catch the event
    const firstEventPromise = waitForMessage(ws, 'vote.received');
    await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: '2026-03-15T20:05:00.000Z',
      });
    await firstEventPromise;

    // Duplicate vote (within 1 second)
    const dupRes = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'down',
        timestamp: '2026-03-15T20:05:00.500Z',
      });

    expect(dupRes.status).toBe(409);

    // Verify no vote.received event comes (wait briefly)
    const noEvent = await Promise.race([
      waitForMessage(ws, 'vote.received', 500).then(() => 'received').catch(() => 'timeout'),
      new Promise((resolve) => setTimeout(() => resolve('timeout'), 500)),
    ]);

    expect(noEvent).toBe('timeout');

    ws.close();
  });

  test('does not broadcast when no active session (404)', 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 res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: '2026-03-15T20:05:00.000Z',
      });

    expect(res.status).toBe(404);

    const noEvent = await Promise.race([
      waitForMessage(ws, 'vote.received', 500).then(() => 'received').catch(() => 'timeout'),
      new Promise((resolve) => setTimeout(() => resolve('timeout'), 500)),
    ]);

    expect(noEvent).toBe('timeout');

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