/**
 * Fix 0-D regression: when a v2 event prompt is emitted on the ONLINE path
 * (PlayerSession.processHourTick), the simulation must pause so time does not
 * keep advancing under an unanswered prompt. This mirrors the OFFLINE path in
 * LoopManager, which saves previousGameSpeed and sets gameSpeed to the pause
 * sentinel (config.SPEED_QUESTION_PAUSE). The resume lives in handlers/events.ts.
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';

// --- Mock the subsystems processHourTick touches so the test is hermetic ---
vi.mock('../../src/stats/stats_manager.js', () => ({
  updateAge: vi.fn(() => null),
  getPeakEnergy: vi.fn((person: { energy?: number; peakEnergy?: number; calcEnergy?: number }) => {
    person.peakEnergy = person.peakEnergy ?? 100;
    person.calcEnergy = (person.energy ?? 100) - (person.peakEnergy ?? 0);
  }),
}));

vi.mock('../../src/game/engine/intradayActivity.js', () => ({
  getIntradayActivity: vi.fn(),
  getDailyPlan: vi.fn(() => []),
}));

vi.mock('../../src/events/conversations/npc_initiative.js', () => ({
  checkNPCInitiatedMessages: vi.fn(async () => undefined),
  checkRelationshipAtRiskNudges: vi.fn(async () => []),
  clearNPCInitiativeState: vi.fn(),
}));

// The crux: stub the event runtime to always return a prompt.
const promptNext = vi.fn(async () => ({
  type: 'event_prompt' as const,
  eventId: 'evt-pause-test',
  prompt: 'A decision awaits.',
  choices: [],
}));
vi.mock('../../src/events/v2/runtime.js', () => ({
  getEventRuntime: () => ({ promptNext }),
}));

import { Person } from '../../src/models/Person.js';
import { Player } from '../../src/models/Player.js';
import { PlayerSession } from '../../src/game/PlayerSession.js';
import { config } from '../../src/config.js';

class MockWebSocket {
  messages: string[] = [];
  readyState = 1; // OPEN
  send(data: string) { this.messages.push(data); }
  close() { this.readyState = 3; }
}

describe('PlayerSession prompt pause (Fix 0-D)', () => {
  let player: Player;
  let ws: MockWebSocket;
  let session: PlayerSession;

  beforeEach(() => {
    promptNext.mockClear();
    const character = new Person({
      id: 'char-1',
      firstname: 'Test',
      lastname: 'Character',
      sex: 'Male',
      ageYears: 25,
      ageDays: 9125,
      energy: 80,
      health: 90,
    });
    player = new Player({
      userId: 'pause-player',
      character,
      status: 'playing',
      date: '2024-06-15',
      hourOfDay: 10,
    });
    // Running at a normal speed (below the pause sentinel) so the event-check
    // branch executes.
    player.gameSpeed = config.SPEED_DEFAULT;
    player.previousGameSpeed = config.SPEED_DEFAULT;

    ws = new MockWebSocket();
    session = new PlayerSession(ws as unknown as never, player);
  });

  it('pauses the loop and saves previousGameSpeed when a prompt is emitted online', async () => {
    expect(player.gameSpeed).toBeLessThan(config.SPEED_QUESTION_PAUSE);

    // Invoke the private hour tick directly.
    await (session as unknown as { processHourTick: () => Promise<void> }).processHourTick();

    // The runtime returned a prompt, so the loop must now be paused.
    expect(promptNext).toHaveBeenCalled();
    expect(player.gameSpeed).toBe(config.SPEED_QUESTION_PAUSE);
    // The pre-pause speed was preserved so the answer handler can restore it.
    expect(player.previousGameSpeed).toBe(config.SPEED_DEFAULT);

    // The prompt itself was actually sent to the client.
    const sent = ws.messages.map((m) => JSON.parse(m));
    expect(sent.some((m) => m.type === 'event_prompt')).toBe(true);
  });

  it('sends a passive event_resolved without pausing the loop (T006a2)', async () => {
    // A passive resolution auto-resolves server-side: there is NO client
    // round-trip to resume the loop, so it must NOT pause or the sim stalls.
    promptNext.mockResolvedValueOnce({
      type: 'event_resolved' as const,
      eventId: 'evt-passive-test',
      instanceId: 'inst-passive-1',
      resolutionText: 'Something happened automatically.',
    } as never);

    expect(player.gameSpeed).toBeLessThan(config.SPEED_QUESTION_PAUSE);

    await (session as unknown as { processHourTick: () => Promise<void> }).processHourTick();

    expect(promptNext).toHaveBeenCalled();
    // No pause: gameSpeed and previousGameSpeed are untouched by the passive path.
    expect(player.gameSpeed).toBe(config.SPEED_DEFAULT);
    expect(player.previousGameSpeed).toBe(config.SPEED_DEFAULT);

    // The resolution was still delivered to the client.
    const sent = ws.messages.map((m) => JSON.parse(m));
    expect(sent.some((m) => m.type === 'event_resolved')).toBe(true);
  });
});
