/**
 * Regression: loadPlayer must PRESERVE a welcome-back digest persisted in the
 * player JSON blob (produced by LoopManager.processOfflineTime).
 *
 * Root cause #2 of "digest never fires for a returning player": loadPlayer
 * unconditionally replaced player.offlineStats with a freshly-computed
 * { minutesOffline, lastOnline } derived from DB updated_at, silently dropping
 * any digest the offline loop had stashed. This test asserts the digest now
 * survives the load while minutesOffline / lastOnline are still recomputed from
 * updated_at.
 */
import { beforeEach, describe, expect, it, vi } from 'vitest';

const {
  mockQueryOne,
  mockGetConnection,
  mockConnection,
} = vi.hoisted(() => {
  const connection = {
    execute: vi.fn().mockResolvedValue(undefined),
    release: vi.fn(),
  };

  return {
    mockQueryOne: vi.fn(),
    mockGetConnection: vi.fn().mockResolvedValue(connection),
    mockConnection: connection,
  };
});

vi.mock('../../src/database/pool.js', () => ({
  getPool: vi.fn(),
  query: vi.fn(),
  queryOne: mockQueryOne,
  execute: vi.fn(),
  getConnection: mockGetConnection,
}));

import { loadPlayer } from '../../src/database/players.js';

const DIGEST = {
  minutesAway: 2880,
  moneyDelta: 1200,
  ageYearsDelta: 0,
  notableEvents: ['You were hired as a barista!'],
  generatedAt: '2026-05-26T12:00:00.000Z',
};

function playerBlob(): string {
  return JSON.stringify({
    userId: 'returning-user',
    c: {
      id: 'char-returning',
      firstname: 'Mia',
      lastname: 'Chen',
      sex: 'Female',
      status: 'alive',
      ageYears: 30,
      ageDays: 10950,
      money: 5000,
    },
    status: 'playing',
    // Persisted digest sitting inside offlineStats from the offline loop.
    offlineStats: {
      minutesOffline: 2880,
      lastOnline: '2026-05-25T12:00:00.000Z',
      digest: DIGEST,
    },
    events: [],
    askedQuestions: [],
  });
}

describe('loadPlayer — welcome-back digest preservation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockGetConnection.mockResolvedValue(mockConnection);
  });

  it('preserves a persisted digest while recomputing minutesOffline/lastOnline', async () => {
    const updatedAt = new Date(Date.now() - 60 * 60 * 1000); // 1 hour ago
    mockQueryOne.mockImplementation(async (sql: string) => {
      if (sql.includes('FROM players')) {
        return {
          id: 'returning-user',
          data: playerBlob(),
          updated_at: updatedAt,
          connection_status: 'disconnected',
        };
      }
      return null;
    });

    const player = await loadPlayer('returning-user');
    expect(player).not.toBeNull();

    // The digest survived the load (was previously clobbered).
    const digest = player!.offlineStats.digest;
    expect(digest).toBeDefined();
    expect(digest!.notableEvents).toEqual(['You were hired as a barista!']);
    expect(digest!.moneyDelta).toBe(1200);
    expect(digest!.minutesAway).toBe(2880);

    // minutesOffline / lastOnline are recomputed from DB updated_at, not the
    // stale value that was in the blob.
    expect(player!.offlineStats.lastOnline?.getTime()).toBe(updatedAt.getTime());
    expect(player!.offlineStats.minutesOffline).toBeGreaterThanOrEqual(59);
    expect(player!.offlineStats.minutesOffline).toBeLessThanOrEqual(61);
  });

  it('produces no digest key when the blob had none', async () => {
    const blob = JSON.parse(playerBlob());
    delete blob.offlineStats.digest;
    mockQueryOne.mockImplementation(async (sql: string) => {
      if (sql.includes('FROM players')) {
        return {
          id: 'returning-user',
          data: JSON.stringify(blob),
          updated_at: new Date(Date.now() - 5 * 60 * 1000),
          connection_status: 'disconnected',
        };
      }
      return null;
    });

    const player = await loadPlayer('returning-user');
    expect(player).not.toBeNull();
    expect(player!.offlineStats.digest).toBeUndefined();
  });
});
