import assert from 'node:assert/strict';
import test from 'node:test';
import { DEFAULT_MATCH } from '../public/js/menu-config.js';
import { DedicatedSupervisor } from '../server/supervisor.js';

function readinessResponder(initialOutput) {
  const serverInfo = [
    "server 'test' IP = 127.0.0.1:28004",
    `si_map = ${DEFAULT_MATCH.map}`,
    `si_gametype = ${DEFAULT_MATCH.mode}`,
    `bot_minplayers = ${DEFAULT_MATCH.botCount}`,
    `si_fragLimit = ${DEFAULT_MATCH.fragLimit}`,
    `si_captureLimit = ${DEFAULT_MATCH.fragLimit}`,
    `si_timeLimit = ${DEFAULT_MATCH.timeLimit}`,
    '',
  ].join('\n');
  return [
    'import os, sys',
    `os.write(1, ${JSON.stringify(initialOutput)}.encode())`,
    'for raw in sys.stdin:',
    ' line = raw.strip()',
    " if line.startswith('echo '): os.write(1, ('\\n' + line[5:] + '\\n').encode())",
    ` elif line == 'serverInfo': os.write(1, ${JSON.stringify(serverInfo)}.encode())`,
  ].join('\n');
}

test('dedicated supervisor backs off after a spawn failure and stops retries', async () => {
  const supervisor = new DedicatedSupervisor({
    enabled: true,
    command: '/definitely/missing/openq4ded',
    args: [],
    cwd: '/',
    backoffBaseMs: 100,
    backoffMaxMs: 200,
    stableMs: 1_000,
    stopTimeoutMs: 100,
  }, { info() {}, warn() {} });

  supervisor.start();
  await new Promise((resolve) => setTimeout(resolve, 30));
  const failed = supervisor.status();
  assert.equal(failed.running, false);
  assert.equal(failed.restartPending, true);
  assert.equal(failed.restartAttempt, 1);
  assert.match(failed.lastExit.error, /ENOENT/);

  await supervisor.stop();
  await new Promise((resolve) => setTimeout(resolve, 130));
  const stopped = supervisor.status();
  assert.equal(stopped.running, false);
  assert.equal(stopped.restartPending, false);
  assert.equal(stopped.restartAttempt, 1);
});

test('dedicated supervisor is not ready until OpenQ4 finishes loading its map', async (t) => {
  const supervisor = new DedicatedSupervisor({
    enabled: true,
    command: '/usr/bin/python3',
    args: ['-c', readinessResponder(
      'starting\nMap: mp/q4web_arena\nnet_LANServer is enabled. Not sending heartbeats\n',
    )],
    cwd: '/',
    port: 28004,
    initialMatch: DEFAULT_MATCH,
    backoffBaseMs: 100,
    backoffMaxMs: 200,
    stableMs: 1_000,
    stopTimeoutMs: 100,
  }, { info() {}, warn() {} });
  t.after(() => supervisor.stop());

  supervisor.start();
  assert.equal(supervisor.status().running, true);
  assert.equal(supervisor.status().ready, false);

  const deadline = Date.now() + 1_000;
  while (!supervisor.status().ready && Date.now() < deadline) {
    await new Promise((resolve) => setTimeout(resolve, 10));
  }
  const ready = supervisor.status();
  assert.equal(ready.running, true);
  assert.equal(ready.ready, true);
  assert.match(ready.readyAt, /^\d{4}-\d{2}-\d{2}T/);

  await supervisor.stop();
  assert.equal(supervisor.status().running, false);
  assert.equal(supervisor.status().ready, false);
});

test('dedicated supervisor finds readiness before the tail of a large output chunk', async (t) => {
  const marker = 'Map: mp/q4web_arena\nnet_LANServer is enabled. Not sending heartbeats\n';
  const supervisor = new DedicatedSupervisor({
    enabled: true,
    command: '/usr/bin/python3',
    args: ['-c', readinessResponder(marker + 'x'.repeat(600))],
    cwd: '/',
    port: 28004,
    initialMatch: DEFAULT_MATCH,
    backoffBaseMs: 100,
    backoffMaxMs: 200,
    stableMs: 1_000,
    stopTimeoutMs: 100,
  }, { info() {}, warn() {} });
  t.after(() => supervisor.stop());

  supervisor.start();
  const deadline = Date.now() + 1_000;
  while (!supervisor.status().ready && Date.now() < deadline) {
    await new Promise((resolve) => setTimeout(resolve, 10));
  }
  assert.equal(supervisor.status().ready, true);
});
