import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { GameView, StateView } from './model';

// Mock the gateway singleton so preload's fan-out is observed without a transport.
const mocks = vi.hoisted(() => ({
  gameState: vi.fn(),
  gameHistory: vi.fn(),
  draftGet: vi.fn(),
}));
vi.mock('./gateway', () => ({
  gateway: { gameState: mocks.gameState, gameHistory: mocks.gameHistory, draftGet: mocks.draftGet },
}));

import { preloadGames } from './preload';
import { clearGameCache, getCachedGame, setCachedGame } from './gamecache';

function gameView(id: string, status: GameView['status'] = 'active'): GameView {
  return {
    id,
    variant: 'scrabble_en',
    dictVersion: 'v1',
    vsAi: false,
    unreadChat: false,
    unreadMessages: false,
    kind: 0,
    status,
    players: 2,
    toMove: 0,
    turnTimeoutSecs: 300,
    multipleWordsPerTurn: true,
    noRepeatWords: false,
    moveCount: 0,
    endReason: '',
    lastActivityUnix: 0,
    seats: [],
  };
}

function stateView(id: string): StateView {
  return { game: gameView(id), seat: 0, rack: ['A', 'B'], bagLen: 50, hintsRemaining: 1 };
}

beforeEach(() => {
  clearGameCache();
  mocks.gameState.mockReset().mockImplementation((id: string) => Promise.resolve(stateView(id)));
  mocks.gameHistory.mockReset().mockImplementation((id: string) => Promise.resolve({ gameId: id, moves: [] }));
  mocks.draftGet.mockReset().mockImplementation((id: string) => Promise.resolve(id === 'g1' ? 'DRAFT1' : ''));
});

describe('preloadGames', () => {
  it('warms ongoing, uncached games with state, history and draft', async () => {
    await preloadGames([gameView('g1'), gameView('g2')]);
    expect(getCachedGame('g1')?.view.game.id).toBe('g1');
    expect(getCachedGame('g1')?.draft).toBe('DRAFT1');
    expect(getCachedGame('g2')?.draft).toBe('');
  });

  it('skips finished games', async () => {
    await preloadGames([gameView('done', 'finished')]);
    expect(getCachedGame('done')).toBeUndefined();
    expect(mocks.gameState).not.toHaveBeenCalled();
  });

  it('skips games already in the cache (kept fresh by the live stream)', async () => {
    setCachedGame('g1', stateView('g1'), [], 'KEEP');
    await preloadGames([gameView('g1'), gameView('g2')]);
    expect(mocks.gameState).toHaveBeenCalledTimes(1);
    expect(mocks.gameState).toHaveBeenCalledWith('g2', expect.any(Boolean));
    expect(getCachedGame('g1')?.draft).toBe('KEEP'); // untouched
  });

  it('does nothing for an empty list', async () => {
    await preloadGames([]);
    expect(mocks.gameState).not.toHaveBeenCalled();
  });
});
