import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { Dawg } from '../dict/dawg';
import { RACK_SIZE } from '../premiums';
import { LocalGame } from './engine';
import { serializeGame, replayGame, type Seat } from './serialize';
import { decide } from '../robot/strategy';
import type { Move } from '../dict/validate';

// A game reconstructed from its record (seed + journal) must be identical to the original — same
// board, racks, bag, scores, turn and log — since the bag is deterministic from the seed and the
// replayed sequence of operations. Uses the committed sample dictionary.
const dawg = new Dawg(new Uint8Array(readFileSync(new URL('../dict/testdata/sample_en.dawg', import.meta.url))));
const seats: Seat[] = [
  { kind: 'human', name: 'You' },
  { kind: 'robot', name: 'Robot' },
];

function bestOpponentScore(game: LocalGame, seat: number): number {
  let best = 0;
  for (let i = 0; i < game.playerCount; i++) if (i !== seat) best = Math.max(best, game.scoreOf(i));
  return best;
}

// drive plays one turn with the robot's choice (legal plays only — see engine.test.ts).
function drive(game: LocalGame, seed: bigint): void {
  const seat = game.currentPlayer;
  const cands: Move[] = game.generateMoves().filter((m) => m.main.letters.length >= 2);
  const dec = decide(seed, game.moveCount, cands, game.scoreOf(seat), bestOpponentScore(game, seat), game.handOf(seat), game.bagLength);
  if (dec.kind === 'play') game.play(dec.move.dir, dec.move.tiles);
  else if (dec.kind === 'exchange' && game.bagLength >= RACK_SIZE) game.exchange(dec.exchange);
  else game.pass();
}

function snapshot(game: LocalGame): unknown {
  const scores: number[] = [];
  const hands: number[][] = [];
  for (let i = 0; i < game.playerCount; i++) {
    scores.push(game.scoreOf(i));
    hands.push(game.handOf(i).slice().sort((a, b) => a - b));
  }
  return { scores, hands, toMove: game.currentPlayer, bagLen: game.bagLength, over: game.isOver, reason: game.endReason, history: game.history };
}

function makeGame(seed: bigint): LocalGame {
  return new LocalGame({ variant: 'scrabble_en', version: 'sample', seed, players: 2, dawg, multipleWords: true });
}

const meta = { id: 'g1', seats, createdAtUnix: 1, updatedAtUnix: 2, hintUnlockAtMs: 0 };

describe('local game serialize + replay', () => {
  it('reconstructs a mid-game position by replay', () => {
    const g = makeGame(777n);
    for (let i = 0; i < 15 && !g.isOver; i++) drive(g, g.seed);
    const rec = serializeGame(g, meta);
    const r = replayGame(rec, dawg);
    expect(snapshot(r)).toEqual(snapshot(g));
  });

  it('reconstructs a finished game by replay', () => {
    const g = makeGame(2024n);
    let t = 0;
    while (!g.isOver && t < 2000) {
      drive(g, g.seed);
      t++;
    }
    expect(g.isOver).toBe(true);
    const rec = serializeGame(g, meta);
    expect(rec.status).toBe('finished');
    const r = replayGame(rec, dawg);
    expect(snapshot(r)).toEqual(snapshot(g));
  });

  it('serialises the seed, rules and journal', () => {
    const g = makeGame(9999999999n);
    for (let i = 0; i < 5 && !g.isOver; i++) drive(g, g.seed);
    const rec = serializeGame(g, meta);
    expect(rec.seed).toBe('9999999999');
    expect(rec.variant).toBe('scrabble_en');
    expect(rec.multipleWords).toBe(true);
    expect(rec.players).toBe(2);
    expect(rec.seats).toEqual(seats);
    expect(rec.journal.length).toBe(g.moveCount);
    expect(replayGame(rec, dawg).seed).toBe(9999999999n);
  });

  it('serialises hotseat metadata: the mode, master PIN and per-seat PINs', async () => {
    const { newLock } = await import('../pin');
    const hostPin = await newLock('9999');
    const seatPin = await newLock('1234');
    const hotseatSeats: Seat[] = [
      { kind: 'human', name: 'Ann', pin: seatPin },
      { kind: 'human', name: 'Bob' },
    ];
    const g = makeGame(5n);
    const rec = serializeGame(g, { id: 'h1', seats: hotseatSeats, hotseat: true, hostPin, createdAtUnix: 1, updatedAtUnix: 2, hintUnlockAtMs: 0 });
    expect(rec.hotseat).toBe(true);
    expect(rec.hostPin).toEqual(hostPin);
    expect(rec.seats[0].pin).toEqual(seatPin);
    expect(rec.seats[1].pin).toBeUndefined();
  });
});
