import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createInitialState, reduce, getLegalIntents, isPlayersTurn } from './game.js';
import { board } from './board.js';

function join(state, playerId, name, seat) {
  return reduce(state, { type: 'JOIN', playerId, name, seat, color: '#000' });
}

/** Drives `playerId` forward by always resolving any pending choice with its
 * first option and otherwise rolling the max die value — walkForward stops
 * early at the next choice/finish regardless of pips, so this reliably makes
 * progress without the test needing to know the board's shape or size. */
function takeTurn(state, playerId) {
  const player = state.players[playerId];
  if (player.pendingChoice) {
    return reduce(state, { type: 'CHOOSE', playerId, spaceId: player.pendingChoice.options[0] });
  }
  return reduce(state, { type: 'ROLL', playerId, value: state.config.diceSides });
}

test('lobby: join validation', () => {
  let state = createInitialState();
  state = join(state, 'p1', 'Alice', 1);
  assert.throws(() => join(state, 'p1', 'Alice again', 2), /already joined/);
  assert.throws(() => join(state, 'p2', 'Bob', 1), /Seat already taken/);
  assert.throws(() => reduce(state, { type: 'START_GAME' }), /Not enough players/);
});

test('turn order and illegal actions', () => {
  let state = createInitialState();
  state = join(state, 'p1', 'Alice', 1);
  state = join(state, 'p2', 'Bob', 2);
  state = reduce(state, { type: 'START_GAME' });

  assert.equal(state.status, 'active');
  assert.deepEqual(state.turnOrder, ['p1', 'p2']);
  assert.equal(state.currentTurn, 'p1');
  assert.equal(isPlayersTurn(state, 'p1'), true);
  assert.equal(isPlayersTurn(state, 'p2'), false);
  assert.deepEqual(getLegalIntents(state, 'p1'), ['REQUEST_ROLL']);
  assert.deepEqual(getLegalIntents(state, 'p2'), []);

  assert.throws(() => reduce(state, { type: 'ROLL', playerId: 'p2', value: 3 }), /Not your turn/);
  assert.throws(() => reduce(state, { type: 'CHOOSE', playerId: 'p1', spaceId: 'anything' }), /No pending choice/);
});

test('full game: every player reaches every fork, race to finish', () => {
  let state = createInitialState();
  state = join(state, 'p1', 'Alice', 1);
  state = join(state, 'p2', 'Bob', 2);
  state = reduce(state, { type: 'START_GAME' });

  const seenChoiceSpaces = new Set();
  let guard = 0;
  while (state.status === 'active') {
    if (++guard > 500) throw new Error('game did not finish in a reasonable number of turns');
    const turnPlayerId = state.currentTurn;
    const before = state.players[turnPlayerId];
    if (before.pendingChoice) seenChoiceSpaces.add(before.position);

    state = takeTurn(state, turnPlayerId);

    const after = state.players[turnPlayerId];
    assert.equal(typeof after.cash, 'number');
    assert.ok(board.spaces[after.position], 'player is always on a real space');
  }

  assert.equal(state.status, 'finished');
  assert.ok(state.winnerId, 'a winner is recorded');
  assert.equal(state.currentTurn, null);
  assert.equal(state.players[state.winnerId].position, 'finish');
  assert.deepEqual(getLegalIntents(state, 'p1'), []);
  assert.deepEqual(getLegalIntents(state, 'p2'), []);
  assert.throws(() => reduce(state, { type: 'ROLL', playerId: 'p1', value: 1 }), /not active/);

  // Always picking the first option at every fork should have visited all
  // three choice points at least once between two full playthroughs' worth
  // of turns (the loser doesn't necessarily finish, so this just checks the
  // race itself exercised real fork logic, not that both players finished).
  assert.ok(seenChoiceSpaces.size >= 1, 'at least one fork was actually resolved');
});

test('board graph is well-formed', () => {
  const ids = Object.keys(board.spaces);
  assert.ok(ids.length > 100, 'the full board should be a substantial expansion of the Phase 1 subset');

  for (const id of ids) {
    const space = board.spaces[id];
    if (space.type === 'choice') {
      assert.ok(Array.isArray(space.choices) && space.choices.length >= 2, `${id} needs 2+ choices`);
      for (const target of space.choices) assert.ok(board.spaces[target], `${id} -> missing ${target}`);
    } else if (space.type === 'finish') {
      assert.equal(space.next, undefined);
    } else {
      assert.ok(board.spaces[space.next], `${id} -> missing ${space.next}`);
    }
  }
});
