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

const root = new URL('../', import.meta.url);
const patchName = '0018-preserve-bot-userinfo-across-map-change.patch';

function 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('map changes recreate persistent bot entities with canonical bot profiles', async () => {
  const patch = await readFile(new URL(`port/native/patches/${patchName}`, root), 'utf8');

  assert.match(patch, /GetRemoteAddress\(\)\.type == NA_BOT/);
  assert.match(patch, /botUserInfo\[i\] = sessLocal\.mapSpawnData\.userInfo\[i\]/);
  assert.match(patch, /sessLocal\.mapSpawnData\.userInfo\[i\] = botUserInfo\[i\]/);
  assert.match(patch, /clients\[i\]\.clientState = SCS_INGAME/);
  assert.match(patch, /idStr\s+botCharacter/);
  assert.match(patch, /client\.botCharacter\.Clear\(\)/);
  assert.match(patch, /clients\[botClientId\]\.botCharacter = \(botName && botName\[0\]\) \? botName : "major"/);
  assert.match(patch, /clients\[i\]\.botCharacter\.IsEmpty\(\) \? "major" : clients\[i\]\.botCharacter\.c_str\(\)/);
  assert.match(patch, /game->ServerClientBegin\( i, true, botCharacter \)/);
  assert.match(patch, /SendUserInfoBroadcast\( i, botUserInfo\[i\], true \)/);
  assert.doesNotMatch(patch, /botUserInfo\[i\]\.GetString\( "ui_name"/);
  assert.match(patch, /bool idAsyncServer::IsBotClient\( int clientNum \) const/);
  assert.match(patch, /bool idNetworkSystem::ServerIsBotClient\(int clientNum\)/);
  assert.match(patch, /virtual bool\s+ServerIsBotClient\(int clientNum\)/);

  const capture = patch.indexOf('botUserInfo[i] = sessLocal.mapSpawnData.userInfo[i]');
  const initialize = patch.indexOf('InitClient( i, clients[i].clientId, clients[i].clientRate )');
  const restore = patch.indexOf('sessLocal.mapSpawnData.userInfo[i] = botUserInfo[i]');
  const inGame = patch.indexOf('clients[i].clientState = SCS_INGAME');
  const begin = patch.indexOf('game->ServerClientBegin( i, true, botCharacter )');
  assert.ok(capture < initialize, 'bot userinfo must be captured before InitClient clears it');
  assert.ok(initialize < restore, 'bot userinfo must be restored after InitClient');
  assert.ok(restore < inGame, 'bot userinfo must be restored before its post-load entity is recreated');
  assert.ok(inGame < begin, 'bot client state must be in-game before its entity is recreated');
});

test('every release path applies the bot userinfo patch to OpenQ4 only', async () => {
  const scripts = await Promise.all([
    readFile(new URL('tools/build-wasm.sh', root), 'utf8'),
    readFile(new URL('tools/build-native.sh', root), 'utf8'),
    readFile(new URL('tools/stage-source-offer.sh', root), 'utf8'),
  ]);

  for (const script of scripts) {
    assert.ok(patchList(script, 'OPENQ4_PATCH_FILES').includes(patchName));
    assert.ok(!patchList(script, 'GAMELIBS_PATCH_FILES').includes(patchName));
  }
});
