import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';

const root = new URL('../', import.meta.url);
const read = (path) => readFileSync(new URL(path, root), 'utf8');
const patchList = (source, variable) => {
  const body = source.match(new RegExp(`${variable}=\\(([\\s\\S]*?)\\n\\)`));
  assert.ok(body, `missing ${variable}`);
  return [...body[1].matchAll(/(\d{4}-[^/"']+\.patch)/g)].map((match) => match[1]);
};

test('bot lifecycle patch releases every owned state and is shared by native and wasm builds', () => {
  const patchName = '0029-release-bot-resources-on-map-change.patch';
  const patch = read(`port/native/patches/${patchName}`);

  assert.match(patch, /rvmBot::~rvmBot[\s\S]*ReleaseBotResources\(\);[\s\S]*UnRegisterBot/);
  assert.match(patch, /BotFreeGoalState\( bs\.gs \);[\s\S]*bs\.gs = 0/);
  assert.match(patch, /BotFreeWeaponState\( bs\.ws \);[\s\S]*bs\.ws = 0/);
  assert.match(patch, /BotFreeWeaponWeights\( ws \);[\s\S]*weaponState->Reset\(\)/);
  assert.match(patch, /FreeWeightConfig\( ws->weaponweightconfig \);/);
  assert.match(patch, /FreeWeightConfig2\( config \);\n\+\tconfig->Reset\(\);/);
  assert.doesNotMatch(patch, /FreeCharacterFile/);

  for (const script of ['tools/build-native.sh', 'tools/build-wasm.sh', 'tools/stage-source-offer.sh']) {
    assert.ok(patchList(read(script), 'GAMELIBS_PATCH_FILES').includes(patchName), `${script} omits ${patchName}`);
  }
  assert.match(read('tools/native-source-identity.py'), new RegExp(`"${patchName.replaceAll('.', '\\.')}"`));
});

test('bot population maintenance removes bots when bot_minplayers becomes zero', () => {
  const patch = read('port/native/patches/0009-port-bots-to-mpgame.patch');
  const start = patch.indexOf('+void idGameLocal::MaintainBotMinPlayers( void )');
  const end = patch.indexOf('\n // RAVEN BEGIN', start);
  assert.notEqual(start, -1);
  assert.notEqual(end, -1);

  const implementation = patch.slice(start, end);
  assert.doesNotMatch(implementation, /targetBots == 0/);
  assert.match(implementation, /while \( botClients > targetBots \)/);
  assert.match(implementation, /RemoveClientSlotForBot\( "was removed by bot_minplayers" \)/);
  assert.ok(
    implementation.indexOf('while ( botClients > targetBots )')
      < implementation.indexOf('if ( botClients >= targetBots || activeClients >= maxPlayers )'),
    'bot removal must run before the add-bot early return',
  );
});

test('native reset smoke exceeds the resource pools across five eight-bot rotations then stays empty', () => {
  const smoke = read('port/native/smoke-bot-minplayers-reset.sh');
  const rotations = smoke.match(/ROTATION_MAPS=\(([\s\S]*?)\n\)/);

  assert.match(smoke, /INITIAL_BOTS=8/);
  assert.ok(rotations, 'missing ROTATION_MAPS');
  assert.ok(
    rotations[1].split('\n').map((line) => line.trim()).filter(Boolean).length >= 5,
    'lifecycle smoke must repopulate eight bots across at least five map rotations',
  );
  assert.match(smoke, /BOT_RESOURCE_POOL_CAPACITY=32/);
  assert.match(smoke, /BOT_LIFETIMES=\$\(\( INITIAL_BOTS \* \(1 \+ \$\{#ROTATION_MAPS\[@\]\}\) \)\)/);
  assert.match(smoke, /BOT_LIFETIMES > BOT_RESOURCE_POOL_CAPACITY/);
  assert.match(smoke, /server-home\/baseoq4\/autoexec\.cfg/);
  assert.match(smoke, /"set net_port \$\{PORT\}"/);
  assert.match(smoke, /assert_network_isolated/);
  assert.match(smoke, /dedicated server escaped the isolated UDP port/);
  assert.match(smoke, /Address already in use\|IPSocketForFamily:/);
  assert.match(smoke, /\+set bot_minplayers "\$\{INITIAL_BOTS\}"/);
  assert.match(smoke, /for map in "\$\{ROTATION_MAPS\[@\]\}"/);
  assert.match(smoke, /spawnServer %s\\n/);
  assert.match(smoke, /wait_for_log_after "\$\{log_offset\}" "msec to load \$\{map\}"/);
  assert.match(smoke, /wait_for_population "ROTATION_\$\{rotation\}"[^\n]+"\$\{INITIAL_BOTS\}" "\$\{map\}"/);
  assert.match(smoke, /printf 'set bot_minplayers 0\\n'/);
  assert.match(smoke, /bots-initial\.serverinfo/);
  assert.match(smoke, /bots-rotation-\$\{rotation\}\.serverinfo/);
  assert.match(smoke, /bots-reset\.serverinfo/);
  assert.match(smoke, /bots-zero-sustained\.serverinfo/);
  assert.match(smoke, /wait_for_population BOTS_REMOVED[^\n]+ 0/);
  assert.match(smoke, /assert_population "\$\{OUT_ROOT\}\/bots-zero-sustained\.serverinfo" 0/);
  assert.match(smoke, /botStatus slots=\$\{expected\} entities=\$\{expected\} active=\$\{expected\}/);
  assert.doesNotMatch(smoke, /systemctl (?:start|restart)/);
});
