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

describe('POST /api/votes/live (regression)', () => {
  let game, session, sessionGame;
  const baseTime = '2026-03-15T20:00:00.000Z';

  beforeEach(() => {
    cleanDb();
    game = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' });
    session = seedSession({ is_active: 1 });
    sessionGame = seedSessionGame(session.id, game.id, {
      status: 'playing',
      played_at: baseTime,
    });
  });

  test('returns 200 with correct response shape for upvote', async () => {
    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(200);
    expect(res.body.success).toBe(true);
    expect(res.body.session).toEqual(
      expect.objectContaining({ id: session.id })
    );
    expect(res.body.game).toEqual(
      expect.objectContaining({
        id: game.id,
        title: 'Quiplash 3',
        upvotes: 1,
        downvotes: 0,
        popularity_score: 1,
      })
    );
    expect(res.body.vote).toEqual({
      username: 'viewer1',
      type: 'up',
      timestamp: '2026-03-15T20:05:00.000Z',
    });

    const voteRow = db.prepare('SELECT * FROM live_votes WHERE username = ?').get('viewer1');
    expect(voteRow).toBeDefined();
    expect(voteRow.session_id).toBe(session.id);
    expect(voteRow.game_id).toBe(game.id);
    expect(voteRow.vote_type).toBe(1);
  });

  test('increments downvotes and decrements popularity_score for downvote', async () => {
    const res = 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.000Z',
      });

    expect(res.status).toBe(200);
    expect(res.body.game.downvotes).toBe(1);
    expect(res.body.game.popularity_score).toBe(-1);
  });

  test('returns 400 for missing username', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({ vote: 'up', timestamp: '2026-03-15T20:05:00.000Z' });

    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/Missing required fields/);
  });

  test('returns 400 for missing vote', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({ username: 'viewer1', timestamp: '2026-03-15T20:05:00.000Z' });

    expect(res.status).toBe(400);
  });

  test('returns 400 for missing timestamp', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({ username: 'viewer1', vote: 'up' });

    expect(res.status).toBe(400);
  });

  test('returns 400 for invalid vote value', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'maybe',
        timestamp: '2026-03-15T20:05:00.000Z',
      });

    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/vote must be either/);
  });

  test('returns 400 for invalid timestamp format', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: 'not-a-date',
      });

    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/Invalid timestamp/);
  });

  test('returns 404 when no active session', async () => {
    cleanDb();
    seedGame({ title: 'Unused' });
    seedSession({ is_active: 0 });

    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);
    expect(res.body.error).toMatch(/No active session/);
  });

  test('returns 404 when vote timestamp does not match any game', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Authorization', getAuthHeader())
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: '2020-01-01T00:00:00.000Z',
      });

    expect(res.status).toBe(404);
    expect(res.body.error).toMatch(/does not match any game/);
  });

  test('returns 409 for duplicate vote within 1 second', async () => {
    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 res = 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(res.status).toBe(409);
    expect(res.body.error).toMatch(/Duplicate vote/);
  });

  test('returns 401 without auth token', async () => {
    const res = await request(app)
      .post('/api/votes/live')
      .set('Content-Type', 'application/json')
      .send({
        username: 'viewer1',
        vote: 'up',
        timestamp: '2026-03-15T20:05:00.000Z',
      });

    expect(res.status).toBe(401);
  });
});
