/**
 * Game speed control handler tests.
 *
 * Regression coverage for task #14: slowing the game down from "instant"
 * sometimes failed and the game stayed stuck at full speed. The cause was a
 * race with the event-driven pause/resume cycle, where previousGameSpeed
 * (the value the resume path restores) still held the stale "instant" value
 * after the user slowed down during a paused question.
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { Person } from '../../src/models/Person';
import { Player } from '../../src/models/Player';
import { config } from '../../src/config';
import { handleSpeed } from '../../src/handlers/gameControl';

class MockPlayerSession {
  player: Player;
  sentMessages: any[] = [];

  constructor(player: Player) {
    this.player = player;
  }

  send(message: any) {
    this.sentMessages.push(message);
  }

  sendPlayerObject() {
    this.sentMessages.push({ type: 'playerObject', player: this.player });
  }

  async savePlayer() {
    return undefined;
  }

  getLastMessage() {
    return this.sentMessages[this.sentMessages.length - 1];
  }
}

function makeSession(gameSpeed: number, previousGameSpeed = gameSpeed): MockPlayerSession {
  const character = new Person({
    id: 'char-1',
    firstname: 'Test',
    lastname: 'Player',
    ageYears: 25,
  });
  const player = new Player({
    userId: 'user-1',
    character,
    status: 'playing',
    controller: 'active',
    gameSpeed,
    previousGameSpeed,
  });
  return new MockPlayerSession(player);
}

const SPEED = config.SPEED_BUTTON_VALUES; // [10000, 1000, 500, 50, 20, 1]
const INSTANT = SPEED[SPEED.length - 1]; // 1 (fastest)
const SLOWEST = SPEED[0]; // 10000

describe('handleSpeed — direction', () => {
  it('"-" from instant makes the game slower (larger tick value)', async () => {
    const session = makeSession(INSTANT);
    await handleSpeed('-', session as any);
    // One step left of instant = SPEED[4] = 20 (still fast, but slower than 1)
    expect(session.player.gameSpeed).toBe(SPEED[SPEED.length - 2]);
    expect(session.player.gameSpeed).toBeGreaterThan(INSTANT);
  });

  it('"+" from slowest makes the game faster (smaller tick value)', async () => {
    const session = makeSession(SLOWEST);
    await handleSpeed('+', session as any);
    expect(session.player.gameSpeed).toBe(SPEED[1]);
    expect(session.player.gameSpeed).toBeLessThan(SLOWEST);
  });

  it('clamps at the fastest end (cannot exceed instant)', async () => {
    const session = makeSession(INSTANT);
    await handleSpeed('+', session as any);
    expect(session.player.gameSpeed).toBe(INSTANT);
  });

  it('clamps at the slowest end', async () => {
    const session = makeSession(SLOWEST);
    await handleSpeed('-', session as any);
    expect(session.player.gameSpeed).toBe(SLOWEST);
  });
});

describe('handleSpeed — paused (event) interaction [task #14 regression]', () => {
  it('slowing down while paused records the new speed as the value to restore', async () => {
    // Simulate: player at instant, a question fires and auto-pauses the game.
    // events/base.ts saves previousGameSpeed = current (instant), then sets
    // gameSpeed = SPEED_QUESTION_PAUSE.
    const session = makeSession(config.SPEED_QUESTION_PAUSE, INSTANT);

    // Player taps "-" to slow down while the question modal is up.
    await handleSpeed('-', session as any);

    // The game must STAY paused (the question modal owns un-pausing on answer).
    expect(session.player.gameSpeed).toBe(config.SPEED_QUESTION_PAUSE);

    // But the value the resume path restores must now be the slower speed,
    // not the stale "instant" value.
    expect(session.player.previousGameSpeed).toBe(SPEED[SPEED.length - 2]); // 20
    expect(session.player.previousGameSpeed).toBeGreaterThan(INSTANT);
  });

  it('after answering, the restored speed is the user-selected slow speed (not instant)', async () => {
    // Full cycle: instant -> question pause -> user slows -> answer resumes.
    const session = makeSession(config.SPEED_QUESTION_PAUSE, INSTANT);

    // User slows down twice while paused.
    await handleSpeed('-', session as any); // -> previousGameSpeed = 20
    await handleSpeed('-', session as any); // -> previousGameSpeed = 50

    expect(session.player.previousGameSpeed).toBe(SPEED[SPEED.length - 3]); // 50

    // Emulate the event-resume path (handlers/events.ts:112-113).
    session.player.gameSpeed = session.player.previousGameSpeed;

    // The game resumes at the user's chosen slow speed, NOT instant.
    expect(session.player.gameSpeed).toBe(SPEED[SPEED.length - 3]); // 50
    expect(session.player.gameSpeed).not.toBe(INSTANT);
  });

  it('speed buttons while paused do not jump to the slowest value', async () => {
    // Before the fix, indexOf(PAUSE sentinel) === -1 collapsed +/- to SPEED[0].
    const session = makeSession(config.SPEED_QUESTION_PAUSE, INSTANT);
    await handleSpeed('+', session as any);
    // "+" from instant stays clamped at instant (not jumped to slowest).
    expect(session.player.previousGameSpeed).toBe(INSTANT);
    expect(session.player.gameSpeed).toBe(config.SPEED_QUESTION_PAUSE);
  });

  it('reports the selected speed to the client while paused', async () => {
    const session = makeSession(config.SPEED_QUESTION_PAUSE, INSTANT);
    await handleSpeed('-', session as any);
    const msg = session.getLastMessage();
    expect(msg.type).toBe('u');
    // Client should see the chosen speed (20), not the pause sentinel.
    expect(msg.gameSpeed).toBe(SPEED[SPEED.length - 2]);
  });
});

describe('handleSpeed — normal updates still send client update', () => {
  it('sends a {type:"u", gameSpeed} update on a normal change', async () => {
    const session = makeSession(SLOWEST);
    await handleSpeed('+', session as any);
    const msg = session.getLastMessage();
    expect(msg.type).toBe('u');
    expect(msg.gameSpeed).toBe(session.player.gameSpeed);
  });

  it('stores previousGameSpeed when changing from a normal (non-paused) speed', async () => {
    const session = makeSession(SPEED[2]); // 500
    await handleSpeed('-', session as any);
    expect(session.player.previousGameSpeed).toBe(SPEED[2]);
    expect(session.player.gameSpeed).toBe(SPEED[1]); // 1000 (slower)
  });
});
