// Smoke test for the headless lifetime simulator. A single short run that
// proves the harness wires correctly and catches its own invariant violations.
//
// Any FAIL here indicates a real bug (or a harness bug that needs to be fixed
// at the harness level, not by silencing assertions).

import { describe, expect, it, vi } from 'vitest';

// -----------------------------------------------------------------------------
// Mocks — must be declared before importing the harness so vitest's module
// hoister rewires PlayerSession's imports transitively.
// -----------------------------------------------------------------------------

vi.mock('../../src/database/players.js', () => ({
  savePlayer: vi.fn(async () => undefined),
  saveConversation: vi.fn(async () => undefined),
  saveConversationMessage: vi.fn(async () => undefined),
  loadPlayer: vi.fn(async () => null),
  loadConversations: vi.fn(async () => []),
  loadPlayerConversations: vi.fn(async () => []),
  markConversationAsRead: vi.fn(async () => undefined),
  updateConnectionStatus: vi.fn(async () => undefined),
  ensureTables: vi.fn(async () => undefined),
  saveGameAsync: vi.fn(async () => undefined),
  loadGameAsync: vi.fn(async () => null),
  loadAllPlayerIds: vi.fn(async () => []),
  loadGames: vi.fn(async () => []),
}));

// In-memory event-instance store shared across calls so the v2 engine can
// create, read back, answer, and resolve instances as if a DB were there.
type MemInstance = {
  instanceId: string;
  eventId: string;
  playerId: string;
  status: 'pending' | 'answered' | 'resolved' | 'cancelled';
  createdAt: string;
  answeredAt?: string;
  resolvedAt?: string;
  selectedChoiceId?: string;
  context?: Record<string, unknown>;
};

const memInstances: MemInstance[] = [];

vi.mock('../../src/database/eventInstances.js', () => ({
  createEventInstance: vi.fn(async (input: Omit<MemInstance, 'createdAt' | 'status'>) => {
    const inst: MemInstance = {
      ...input,
      createdAt: new Date().toISOString(),
      status: 'pending',
    };
    memInstances.push(inst);
    return inst;
  }),
  getPendingEventInstances: vi.fn(async (playerId: string) => {
    return memInstances.filter((i) => i.playerId === playerId && i.status === 'pending');
  }),
  answerEventInstance: vi.fn(async (instanceId: string, choiceId: string) => {
    const inst = memInstances.find((i) => i.instanceId === instanceId);
    if (!inst) return null;
    inst.status = 'answered';
    inst.answeredAt = new Date().toISOString();
    inst.selectedChoiceId = choiceId;
    return inst;
  }),
  resolveEventInstance: vi.fn(async (instanceId: string) => {
    const inst = memInstances.find((i) => i.instanceId === instanceId);
    if (!inst) return null;
    inst.status = 'resolved';
    inst.resolvedAt = new Date().toISOString();
    return inst;
  }),
  ensureEventInstancesTable: vi.fn(async () => undefined),
}));

vi.mock('../../src/services/notifications/notificationManager.js', () => ({
  notifyRealtimeEvent: vi.fn(async () => undefined),
  queueRealtimeNotification: vi.fn(async () => undefined),
  clearThrottle: vi.fn(() => undefined),
  notificationManager: {},
}));

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

vi.mock('../../src/services/retention/integration.js', () => ({
  onJobObtained: vi.fn(async () => undefined),
  onPromotion: vi.fn(async () => undefined),
  onFired: vi.fn(async () => undefined),
  onMarriage: vi.fn(async () => undefined),
  onDating: vi.fn(async () => undefined),
  onChildBorn: vi.fn(async () => undefined),
  onFriendMade: vi.fn(async () => undefined),
  onBirthday: vi.fn(async () => undefined),
  onGraduation: vi.fn(async () => undefined),
}));

// -----------------------------------------------------------------------------
// Harness import (after mocks).
// -----------------------------------------------------------------------------

import { simulateLifetime } from './lifetime-simulator.js';

describe('lifetime simulator smoke', () => {
  it('runs one in-game week without throwing', async () => {
    const result = await simulateLifetime({
      seed: 1,
      maxMinutes: 7 * 24 * 60, // one week
    });

    // Errors are hard failures — real crashes from the game loop.
    expect(result.errors, `Errors: ${JSON.stringify(result.errors.slice(0, 5), null, 2)}`).toEqual([]);

    // Invariants are softer — we report them but a handful from edge cases
    // early in Phase 1 are expected and will be cleaned up in Phase 2.
    expect(result.minutesSimulated).toBeGreaterThan(0);
  }, 30_000);
});
