// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * NYU CTF Bench — Challenge loader for the NYU CTF benchmark suite.
 *
 * Loads challenges from the NYU CTF Bench format (NeurIPS 2024):
 * - challenge.json with name, description, category, difficulty, flag
 * - Per-challenge docker-compose.yaml for target services
 * - 6 categories: web, pwn, rev, crypto, forensics, misc
 * - 255 challenges across 5 difficulty levels
 *
 * @module benchmark/nyu-ctf
 */

import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join, basename } from 'node:path';
import { homedir } from 'node:os';
import { BenchmarkConfig } from './models.js';
import { CompetitorBaseline } from './models.js';

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

export const NYU_CTF_REPO_URL = 'https://github.com/NYU-LLM-CTF/LLM_CTF_Database.git';
export const NYU_CTF_CLONE_DIR = join(homedir(), '.nerf', 'benchmarks', 'nyu-ctf-bench');

/** NYU CTF categories with tag mappings. */
export const NYU_CATEGORIES = ['web', 'pwn', 'rev', 'crypto', 'forensics', 'misc'];

/** Difficulty mapping: NYU uses string labels, we use 1-5. */
const DIFFICULTY_MAP = {
  'easy': 1,
  'medium': 2,
  'hard': 3,
  'very hard': 4,
  'expert': 5,
  // Numeric fallbacks
  '1': 1, '2': 2, '3': 3, '4': 4, '5': 5,
};

// ---------------------------------------------------------------------------
// NYU CTF Baselines
// ---------------------------------------------------------------------------

export const CRAKEN_BASELINE = new CompetitorBaseline({
  name: 'CRAKEN',
  overallPct: 22.0,
  overallPassed: 56,
  overallTotal: 255,
  levelPct: {},
  medianCostUsd: 0,
  medianTimeS: 0,
  source: 'NYU CTF Bench — CRAKEN (NeurIPS 2024)',
});

export const DNERF_BASELINE = new CompetitorBaseline({
  name: 'D-NERF',
  overallPct: 19.0,
  overallPassed: 48,
  overallTotal: 255,
  levelPct: {},
  medianCostUsd: 0,
  medianTimeS: 0,
  source: 'NYU CTF Bench — D-NERF (NeurIPS 2024)',
});

export const ENIGMA_BASELINE = new CompetitorBaseline({
  name: 'EnIGMA',
  overallPct: 13.5,
  overallPassed: 34,
  overallTotal: 255,
  levelPct: {},
  medianCostUsd: 0,
  medianTimeS: 0,
  source: 'NYU CTF Bench — EnIGMA (NeurIPS 2024)',
});

export const NYU_BASELINES = [CRAKEN_BASELINE, DNERF_BASELINE, ENIGMA_BASELINE];

// ---------------------------------------------------------------------------
// Challenge loader
// ---------------------------------------------------------------------------

/**
 * Parse a NYU CTF challenge.json file into a BenchmarkConfig.
 *
 * Expected format:
 * {
 *   "name": "challenge-name",
 *   "description": "Challenge description",
 *   "category": "web",
 *   "difficulty": "medium",
 *   "flag": "flag{...}",
 *   "files": ["file1.py", "file2.txt"],
 *   "docker_compose": true
 * }
 *
 * @param {string} configPath - Path to challenge.json
 * @returns {BenchmarkConfig}
 */
export function loadNyuChallenge(configPath) {
  const data = JSON.parse(readFileSync(configPath, 'utf8'));
  const dir = configPath.replace(/\/[^/]+$/, '');

  const category = (data.category || 'misc').toLowerCase();
  const difficultyStr = String(data.difficulty || 'medium').toLowerCase();
  const level = DIFFICULTY_MAP[difficultyStr] ?? 2;

  // Build tags from category + any explicit tags
  const tags = [category];
  if (data.tags) tags.push(...data.tags);

  // Detect files
  const files = (data.files || []).map(f => (typeof f === 'string' ? { name: f } : f));

  return new BenchmarkConfig({
    name: data.name || basename(dir),
    description: data.description || '',
    level: Math.min(5, Math.max(1, level)),
    tags,
    winCondition: 'flag',
    files,
    path: dir,
    category,
  });
}

/**
 * Enumerate all NYU CTF challenges in a directory.
 *
 * Walks the directory tree looking for challenge.json files.
 * Supports both flat (all in one dir) and nested (category/challenge/) layouts.
 *
 * @param {string} baseDir - Root directory to scan
 * @returns {BenchmarkConfig[]}
 */
export function enumerateNyuChallenges(baseDir) {
  const configs = [];
  if (!existsSync(baseDir)) return configs;

  function walk(dir, depth = 0) {
    if (depth > 3) return; // Don't recurse too deep
    for (const entry of readdirSync(dir).sort()) {
      const entryPath = join(dir, entry);
      if (!statSync(entryPath).isDirectory() && entry === 'challenge.json') {
        try {
          configs.push(loadNyuChallenge(entryPath));
        } catch { /* skip malformed */ }
        return; // Don't recurse further in this dir
      }
    }
    // Recurse into subdirectories
    for (const entry of readdirSync(dir).sort()) {
      const entryPath = join(dir, entry);
      if (statSync(entryPath).isDirectory()) {
        walk(entryPath, depth + 1);
      }
    }
  }

  walk(baseDir);
  return configs;
}

/**
 * Get the expected flag for a NYU CTF challenge.
 *
 * @param {string} configPath - Path to challenge.json
 * @returns {string|null}
 */
export function getNyuFlag(configPath) {
  try {
    const data = JSON.parse(readFileSync(configPath, 'utf8'));
    return data.flag || null;
  } catch {
    return null;
  }
}

/**
 * Check if a directory has a docker-compose file for the challenge.
 *
 * @param {string} challengeDir
 * @returns {boolean}
 */
export function hasDockerCompose(challengeDir) {
  return existsSync(join(challengeDir, 'docker-compose.yml'))
    || existsSync(join(challengeDir, 'docker-compose.yaml'));
}
