import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';

import { loadConfig } from '../server/config.js';
import { listReplays, pruneReplays } from '../server/replays.js';

function fixtureDir(t) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'q4-replays-'));
  t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
  return dir;
}

function writeReplay(dir, name, ageSeconds, metadata = null) {
  const replay = path.join(dir, name);
  fs.writeFileSync(replay, Buffer.from('Q4SD'));
  const stamp = new Date(Date.now() - ageSeconds * 1000);
  fs.utimesSync(replay, stamp, stamp);
  if (metadata) fs.writeFileSync(`${replay}.meta.json`, JSON.stringify(metadata));
}

test('replay listing publishes only finalized OpenQ4 server demos', (t) => {
  const dir = fixtureDir(t);
  writeReplay(dir, 'match.q4sd', 1, { complete: true, map: 'mp/q4dm1' });
  fs.writeFileSync(path.join(dir, 'recording.q4sd.part'), 'partial');
  fs.writeFileSync(path.join(dir, 'legacy.q4r'), 'wire capture');
  fs.writeFileSync(path.join(dir, 'render.demo'), 'not a server demo');

  const replays = listReplays(dir);
  assert.equal(replays.length, 1);
  assert.equal(replays[0].name, 'match.q4sd');
  assert.deepEqual(replays[0].metadata, { complete: true, map: 'mp/q4dm1' });
  assert.match(replays[0].deepLink, /^\/\?replay=/);
  assert.equal(replays[0].watchUrl, `/api/replays/${replays[0].id}/watch`);
});

test('retention keeps newest demos and removes their metadata sidecars', (t) => {
  const dir = fixtureDir(t);
  for (let i = 0; i < 5; i++) writeReplay(dir, `match-${i}.q4sd`, i, { index: i });

  const removed = pruneReplays(dir, 3);
  assert.deepEqual(removed, ['match-3.q4sd', 'match-4.q4sd']);
  assert.deepEqual(listReplays(dir, 3).map((item) => item.name), [
    'match-0.q4sd',
    'match-1.q4sd',
    'match-2.q4sd',
  ]);
  assert.equal(fs.existsSync(path.join(dir, 'match-4.q4sd.meta.json')), false);
});

test('dedicated defaults enable full-state demos in the configured replay directory', () => {
  const config = loadConfig({ env: {}, rootDir: '/tmp/q4-config-test' });
  assert.equal(config.replayRetention, 300);
  assert.deepEqual(config.replayAllocation, { maxNewPerIp: 2, windowMs: 10 * 60_000 });
  assert.deepEqual([...config.replayPlayback.ports], [28040, 28041, 28042, 28043, 28044, 28045, 28046, 28047]);
  assert.equal(config.replayPlayback.enabled, true);
  assert.equal(config.replayPlayback.startTimeoutMs, 120_000);
  const demoIndex = config.dedicated.args.indexOf('net_serverDemo') - 1;
  assert.deepEqual(config.dedicated.args.slice(demoIndex, demoIndex + 6), [
    '+set', 'net_serverDemo', '1',
    '+set', 'net_serverDemoPath', '/tmp/q4-config-test/replays',
  ]);
  const lanIndex = config.dedicated.args.indexOf('net_LANServer') - 1;
  assert.deepEqual(config.dedicated.args.slice(lanIndex, lanIndex + 9), [
    '+set', 'net_LANServer', '1',
    '+set', 'net_serverClientTimeout', '180',
    '+set', 'si_pure', '0',
  ]);
  const valueAfter = (name) => config.dedicated.args[config.dedicated.args.indexOf(name) + 1];
  assert.equal(valueAfter('bot_debug'), '0');
  assert.equal(valueAfter('bot_minplayers'), '8');
  assert.equal(valueAfter('si_maxPlayers'), '12');
  assert.equal(valueAfter('si_map'), 'mp/q4web_arena');
  assert.equal(valueAfter('si_gameType'), 'DM');
  assert.equal(valueAfter('net_serverAllowDataChecksumMismatch'), '1');
  assert.equal(valueAfter('net_serverClientTimeout'), '180');
  assert.equal(valueAfter('fs_caseSensitiveOS'), '0');
  assert.equal(valueAfter('s_noSound'), '1');
});

test('replay allocation guard configuration clamps env and override values', () => {
  const minimum = loadConfig({
    env: {
      Q4_REPLAY_NEW_ALLOCATIONS_PER_IP: '0',
      Q4_REPLAY_ALLOCATION_WINDOW_MS: '1',
    },
    rootDir: '/tmp/q4-config-minimum',
  });
  assert.deepEqual(minimum.replayAllocation, { maxNewPerIp: 1, windowMs: 10_000 });

  const maximum = loadConfig({
    env: {},
    rootDir: '/tmp/q4-config-maximum',
    replayAllocationLimit: 1_000,
    replayAllocationWindowMs: 100 * 24 * 60 * 60_000,
  });
  assert.deepEqual(maximum.replayAllocation, { maxNewPerIp: 100, windowMs: 24 * 60 * 60_000 });
});
