import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';

import {
  BOT_NAVIGATION_COMPILER_PROFILES,
  BOT_NAVIGATION_PLAYERCLIP_COMPILER_PROFILE,
  BOT_NAVIGATION_PLAYERCLIP_OVERRIDE_PATH,
  BOT_NAVIGATION_PLAYERCLIP_OVERRIDE_SOURCE,
  BOT_NAVIGATION_TEAM_SEED_PATH,
  BOT_NAVIGATION_TEAM_SEED_SOURCE,
} from '../server/bot-navigation.js';

export async function stageBotNavigationCompilerSeed(saveRoot) {
  const seedFile = path.join(saveRoot, BOT_NAVIGATION_TEAM_SEED_PATH);
  await mkdir(path.dirname(seedFile), { recursive: true });
  await writeFile(seedFile, BOT_NAVIGATION_TEAM_SEED_SOURCE, { flag: 'wx', mode: 0o600 });
  return seedFile;
}

export async function stageBotNavigationCompilerProfile(saveRoot, requestedProfile) {
  const profile = BOT_NAVIGATION_COMPILER_PROFILES.find(({ id, sha256 }) =>
    id === requestedProfile?.id && sha256 === requestedProfile?.sha256);
  if (!profile || profile.kind !== 'compiler-override') {
    throw new Error('unknown bot navigation compiler profile');
  }

  const files = [await stageBotNavigationCompilerSeed(saveRoot)];
  if (profile.id === BOT_NAVIGATION_PLAYERCLIP_COMPILER_PROFILE.id) {
    const overrideFile = path.join(saveRoot, BOT_NAVIGATION_PLAYERCLIP_OVERRIDE_PATH);
    await mkdir(path.dirname(overrideFile), { recursive: true });
    await writeFile(overrideFile, BOT_NAVIGATION_PLAYERCLIP_OVERRIDE_SOURCE, { flag: 'wx', mode: 0o600 });
    files.push(overrideFile);
  }
  return { profile, files };
}

export function botNavigationAttemptFailed({
  log = '',
  outputReady = false,
  status = 0,
  signal = null,
  errorCode = null,
} = {}) {
  return !outputReady ||
    /reached outside from entity|\.map has no outside/i.test(String(log)) ||
    errorCode === 'ETIMEDOUT' ||
    signal !== null ||
    status !== 0;
}

export function botNavigationAttemptIsKnownLeak({
  log = '',
  outputPresent = false,
  status = 0,
  signal = null,
  errorCode = null,
} = {}, map) {
  if (!/^[A-Za-z0-9_/-]+$/.test(String(map || ''))) return false;
  const source = String(log);
  return !outputPresent &&
    status === 0 &&
    signal === null &&
    errorCode === null &&
    /reached outside from entity \d+ \([A-Za-z0-9_]+\)/.test(source) &&
    source.includes(`maps/${map}.map has no outside`);
}

export function botNavigationAttemptIsExactPostWriteAbort({
  log = '',
  outputReady = false,
  status = 0,
  signal = null,
  errorCode = null,
} = {}, map) {
  if (!/^[A-Za-z0-9_/-]+$/.test(String(map || ''))) return false;
  const source = String(log);
  return outputReady &&
    status === 134 &&
    signal === null &&
    errorCode === null &&
    source.includes('[Write AAS]') &&
    source.includes(`writing maps/${map}.aas32`) &&
    /\bdone\.\s*\n\s*\d+ seconds to create AAS\b/.test(source) &&
    /^(?:corrupted double-linked list|corrupted size vs\. prev_size)$/m.test(source) &&
    source.includes('openQ4: fatal signal SIGABRT (6), exiting without unsafe engine shutdown') &&
    !/reached outside from entity|\.map has no outside/i.test(source);
}

export function botNavigationXvfbArgs({ client, runtimeRoot, saveRoot, map }) {
  return [
    '-a', client,
    '+set', 'fs_basepath', runtimeRoot,
    '+set', 'fs_cdpath', runtimeRoot,
    '+set', 'fs_savepath', saveRoot,
    '+set', 'fs_game', 'baseoq4',
    '+set', 'fs_caseSensitiveOS', '0',
    '+set', 'r_fullscreen', '0',
    '+set', 's_noSound', '1',
    '+runAAS', map,
    '+quit',
  ];
}
