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

/**
 * PicoCTF — Challenge loader for CMU's picoCTF benchmark suite.
 *
 * PicoCTF challenges are hosted remotely on picoctf.org / picoGym.
 * This loader builds a challenge catalog from:
 * 1. A bundled catalog of known challenges with metadata
 * 2. Community writeup repos for solution verification
 *
 * Categories: Cryptography, Forensics, Binary Exploitation,
 *             Web Exploitation, Reverse Engineering, General Skills
 *
 * Flag format: picoCTF{...}
 *
 * @module benchmark/picoctf
 */

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

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

export const PICOCTF_DATA_DIR = join(homedir(), '.nerf', 'benchmarks', 'picoctf');
export const PICOCTF_CATALOG_FILE = join(PICOCTF_DATA_DIR, 'catalog.json');

/** PicoCTF categories mapped to our standard taxonomy. */
export const PICO_CATEGORIES = [
  'cryptography',
  'forensics',
  'binary-exploitation',
  'web-exploitation',
  'reverse-engineering',
  'general-skills',
];

/** Category aliases (picoCTF uses various naming conventions). */
const CATEGORY_ALIASES = {
  'crypto':               'cryptography',
  'cryptography':         'cryptography',
  'forensics':            'forensics',
  'pwn':                  'binary-exploitation',
  'binary exploitation':  'binary-exploitation',
  'binary_exploitation':  'binary-exploitation',
  'binaryexploitation':   'binary-exploitation',
  'web':                  'web-exploitation',
  'web exploitation':     'web-exploitation',
  'web_exploitation':     'web-exploitation',
  'webexploitation':      'web-exploitation',
  'rev':                  'reverse-engineering',
  'reverse engineering':  'reverse-engineering',
  'reverse_engineering':  'reverse-engineering',
  'reverseengineering':   'reverse-engineering',
  'general skills':       'general-skills',
  'general_skills':       'general-skills',
  'generalskills':        'general-skills',
  'misc':                 'general-skills',
};

/**
 * Difficulty mapping from point values to 1-5 scale.
 * PicoCTF uses point values: 10-50 easy, 50-150 medium, 150-300 hard, 300-500 very hard.
 */
function pointsToLevel(points) {
  if (points <= 50)  return 1;
  if (points <= 150) return 2;
  if (points <= 300) return 3;
  if (points <= 500) return 4;
  return 5;
}

/** Normalize a category string. */
function normalizeCategory(raw) {
  return CATEGORY_ALIASES[raw.toLowerCase().trim()] || 'general-skills';
}

// ---------------------------------------------------------------------------
// PicoCTF challenge entry
// ---------------------------------------------------------------------------

/**
 * @typedef {object} PicoCTFChallenge
 * @property {string} name        - Challenge name
 * @property {string} category    - Normalized category
 * @property {number} points      - Point value
 * @property {string} event       - Event name (e.g. 'picoCTF 2024')
 * @property {string} description - Challenge description
 * @property {string} [host]      - Remote host (if network challenge)
 * @property {number} [port]      - Remote port
 * @property {string} targetType  - 'ssh' | 'netcat' | 'http' | 'static' | 'unknown'
 * @property {string[]} hints     - Challenge hints
 * @property {string[]} files     - Downloadable file URLs
 * @property {string} [flag]      - Known flag (for validation)
 */

// ---------------------------------------------------------------------------
// Bundled challenge catalog
// ---------------------------------------------------------------------------

/**
 * Built-in catalog of PicoCTF challenges.
 * This is a representative subset covering all categories and difficulty levels.
 * The full picoGym has 400+ challenges; this catalog focuses on ones suitable
 * for automated agent benchmarking.
 *
 * Organized by event year → challenges.
 */
export const PICOCTF_CATALOG = [
  // --- General Skills (beginner) ---
  { name: 'Obedient Cat', event: 'picoCTF 2021', category: 'general-skills', points: 5, targetType: 'static', description: 'Download and read a file to find the flag.' },
  { name: 'Python Wrangling', event: 'picoCTF 2021', category: 'general-skills', points: 10, targetType: 'static', description: 'Use a Python script with a password to decrypt the flag.' },
  { name: 'Wave a Flag', event: 'picoCTF 2021', category: 'general-skills', points: 10, targetType: 'static', description: 'Execute a program to get the flag.' },
  { name: 'Nice Netcat', event: 'picoCTF 2021', category: 'general-skills', points: 15, targetType: 'netcat', description: 'Connect with netcat and decode ASCII values.' },
  { name: 'Static aint always noise', event: 'picoCTF 2021', category: 'general-skills', points: 20, targetType: 'static', description: 'Use a disassembly script to find the flag in a binary.' },
  { name: 'Tab, Tab, Attack', event: 'picoCTF 2021', category: 'general-skills', points: 20, targetType: 'static', description: 'Navigate deeply nested directories to find executable.' },
  { name: 'Magikarp Ground Mission', event: 'picoCTF 2021', category: 'general-skills', points: 30, targetType: 'ssh', description: 'SSH into a machine and find flag pieces.' },
  { name: 'Serpentine', event: 'picoCTF 2022', category: 'general-skills', points: 100, targetType: 'static', description: 'Modify Python source to bypass menu and call flag function.' },
  { name: 'Based', event: 'picoCTF 2019', category: 'general-skills', points: 200, targetType: 'netcat', description: 'Convert between binary, octal, and hex encodings.' },
  { name: 'First Find', event: 'picoCTF 2024', category: 'general-skills', points: 100, targetType: 'static', description: 'Unzip and find a specific file in a directory structure.' },

  // --- Cryptography ---
  { name: 'Mod 26', event: 'picoCTF 2021', category: 'cryptography', points: 10, targetType: 'static', description: 'Apply ROT13 to decode the flag.' },
  { name: 'Mind your Ps and Qs', event: 'picoCTF 2021', category: 'cryptography', points: 20, targetType: 'static', description: 'Factor small RSA modulus and decrypt.' },
  { name: 'Easy1', event: 'picoCTF 2019', category: 'cryptography', points: 100, targetType: 'static', description: 'Decrypt a one-time pad cipher with a given key table.' },
  { name: 'New Caesar', event: 'picoCTF 2021', category: 'cryptography', points: 60, targetType: 'static', description: 'Reverse a custom Base16 + shift cipher.' },
  { name: 'Mini RSA', event: 'picoCTF 2021', category: 'cryptography', points: 70, targetType: 'static', description: 'Small public exponent RSA attack.' },
  { name: 'Substitution0', event: 'picoCTF 2022', category: 'cryptography', points: 100, targetType: 'static', description: 'Decode a simple substitution cipher.' },
  { name: 'transposition-trial', event: 'picoCTF 2022', category: 'cryptography', points: 100, targetType: 'static', description: 'Reverse a transposition cipher.' },
  { name: 'C3', event: 'picoCTF 2024', category: 'cryptography', points: 200, targetType: 'static', description: 'Custom cipher with Python encoder/decoder.' },
  { name: 'rsa_oracle', event: 'picoCTF 2023', category: 'cryptography', points: 300, targetType: 'netcat', description: 'Use RSA oracle for chosen-ciphertext attack.' },

  // --- Web Exploitation ---
  { name: 'GET aHEAD', event: 'picoCTF 2021', category: 'web-exploitation', points: 20, targetType: 'http', description: 'Use HTTP HEAD request to find the flag.' },
  { name: 'Cookies', event: 'picoCTF 2021', category: 'web-exploitation', points: 40, targetType: 'http', description: 'Manipulate cookie values to find the flag.' },
  { name: 'Insp3ct0r', event: 'picoCTF 2019', category: 'web-exploitation', points: 50, targetType: 'http', description: 'Inspect page source, CSS, and JS for flag parts.' },
  { name: 'Scavenger Hunt', event: 'picoCTF 2021', category: 'web-exploitation', points: 50, targetType: 'http', description: 'Find flag pieces in HTML, CSS, JS, robots.txt, .htaccess.' },
  { name: 'SQL Direct', event: 'picoCTF 2022', category: 'web-exploitation', points: 200, targetType: 'netcat', description: 'Connect to PostgreSQL and query for the flag.' },
  { name: 'Web Gauntlet', event: 'picoCTF 2020', category: 'web-exploitation', points: 170, targetType: 'http', description: 'SQL injection with increasing filter bypass rounds.' },
  { name: 'JAuth', event: 'picoCTF 2022', category: 'web-exploitation', points: 300, targetType: 'http', description: 'JWT none algorithm bypass for privilege escalation.' },
  { name: 'Java Code Analysis', event: 'picoCTF 2023', category: 'web-exploitation', points: 300, targetType: 'http', description: 'Analyze Java source to find JWT weak secret.' },
  { name: 'Crack the Gate 1', event: 'picoCTF 2025', category: 'web-exploitation', points: 100, targetType: 'http', description: 'Find dev backdoor header in page source.' },

  // --- Forensics ---
  { name: 'information', event: 'picoCTF 2021', category: 'forensics', points: 10, targetType: 'static', description: 'Extract flag from image EXIF metadata.' },
  { name: 'Matryoshka doll', event: 'picoCTF 2021', category: 'forensics', points: 30, targetType: 'static', description: 'Recursively extract hidden files from images.' },
  { name: 'Wireshark doo dooo', event: 'picoCTF 2021', category: 'forensics', points: 50, targetType: 'static', description: 'Analyze a pcap file to find the flag.' },
  { name: 'Trivial Flag Transfer Protocol', event: 'picoCTF 2021', category: 'forensics', points: 90, targetType: 'static', description: 'Extract files from TFTP pcap and decode.' },
  { name: 'St3g0', event: 'picoCTF 2022', category: 'forensics', points: 200, targetType: 'static', description: 'Extract hidden data from PNG using steganography.' },
  { name: 'Sleuthkit Intro', event: 'picoCTF 2022', category: 'forensics', points: 100, targetType: 'static', description: 'Use mmls and other Sleuthkit tools on a disk image.' },
  { name: 'Disk, disk, sleuth!', event: 'picoCTF 2021', category: 'forensics', points: 110, targetType: 'static', description: 'Analyze a disk image with Sleuthkit.' },

  // --- Reverse Engineering ---
  { name: 'Transformation', event: 'picoCTF 2021', category: 'reverse-engineering', points: 20, targetType: 'static', description: 'Reverse a Unicode encoding transformation.' },
  { name: 'vault-door-training', event: 'picoCTF 2019', category: 'reverse-engineering', points: 50, targetType: 'static', description: 'Read Java source to find hardcoded password.' },
  { name: 'GDB baby step 1', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Use GDB to find value in eax register.' },
  { name: 'patchme.py', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Read Python source to find the correct password.' },
  { name: 'Safe Opener', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Decode Base64 password from Java source.' },
  { name: 'Reverse', event: 'picoCTF 2024', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Use strings or disassemble binary to find flag.' },
  { name: 'asm1', event: 'picoCTF 2019', category: 'reverse-engineering', points: 200, targetType: 'static', description: 'Trace x86 assembly to compute return value.' },
  { name: 'Keygenme', event: 'picoCTF 2022', category: 'reverse-engineering', points: 400, targetType: 'static', description: 'Reverse a key validation algorithm in a binary.' },

  // --- Binary Exploitation ---
  { name: 'Stonks', event: 'picoCTF 2021', category: 'binary-exploitation', points: 20, targetType: 'netcat', description: 'Format string vulnerability to leak flag from stack.' },
  { name: 'buffer overflow 0', event: 'picoCTF 2022', category: 'binary-exploitation', points: 100, targetType: 'netcat', description: 'Basic stack buffer overflow to trigger win function.' },
  { name: 'buffer overflow 1', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: 'Buffer overflow to overwrite return address.' },
  { name: 'RPS', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: 'Win 5 consecutive games via binary analysis.' },
  { name: 'x-sixty-what', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: '64-bit buffer overflow to call flag function.' },
  { name: 'format string 0', event: 'picoCTF 2024', category: 'binary-exploitation', points: 50, targetType: 'netcat', description: 'Exploit a format string vulnerability.' },
  { name: 'heap 0', event: 'picoCTF 2024', category: 'binary-exploitation', points: 50, targetType: 'netcat', description: 'Heap overflow to overwrite adjacent data.' },
];

// ---------------------------------------------------------------------------
// Loader
// ---------------------------------------------------------------------------

/**
 * Load a PicoCTF challenge entry into a BenchmarkConfig.
 *
 * @param {PicoCTFChallenge} entry
 * @returns {BenchmarkConfig}
 */
export function loadPicoChallenge(entry) {
  const category = normalizeCategory(entry.category);
  const level = entry.level ?? pointsToLevel(entry.points ?? 100);
  const tags = [category, 'picoctf'];
  if (entry.event) tags.push(entry.event.toLowerCase().replace(/\s+/g, '-'));

  return new BenchmarkConfig({
    name: entry.name,
    description: entry.description || '',
    level: Math.min(5, Math.max(1, level)),
    tags,
    winCondition: 'flag',
    files: (entry.files || []).map(f => typeof f === 'string' ? { name: f } : f),
    path: entry.path || PICOCTF_DATA_DIR,
    category,
  });
}

/**
 * Load all challenges from the bundled catalog.
 * @returns {BenchmarkConfig[]}
 */
export function enumeratePicoChallenges() {
  // First try loading from disk catalog (may have been enriched)
  if (existsSync(PICOCTF_CATALOG_FILE)) {
    try {
      const catalog = JSON.parse(readFileSync(PICOCTF_CATALOG_FILE, 'utf8'));
      return catalog.map(loadPicoChallenge);
    } catch { /* fall through to bundled */ }
  }

  // Use bundled catalog
  return PICOCTF_CATALOG.map(loadPicoChallenge);
}

/**
 * Save an enriched catalog to disk.
 * @param {PicoCTFChallenge[]} challenges
 */
export function savePicoCatalog(challenges) {
  mkdirSync(PICOCTF_DATA_DIR, { recursive: true });
  // Atomic write (BENCH-009)
  const tmpFile = `${PICOCTF_CATALOG_FILE}.tmp`;
  writeFileSync(tmpFile, JSON.stringify(challenges, null, 2));
  renameSync(tmpFile, PICOCTF_CATALOG_FILE);
}

/**
 * Initialize PicoCTF data directory and save bundled catalog.
 * @returns {boolean}
 */
export function clonePicoCTF() {
  mkdirSync(PICOCTF_DATA_DIR, { recursive: true });
  savePicoCatalog(PICOCTF_CATALOG);
  return true;
}

/**
 * Get target connection info for a PicoCTF challenge.
 *
 * @param {PicoCTFChallenge} entry
 * @returns {{ type: string, host: string, port: number }|null}
 */
export function getPicoTargetInfo(entry) {
  if (entry.targetType === 'static') return null;
  return {
    type: entry.targetType || 'unknown',
    host: entry.host || 'saturn.picoctf.net',
    port: entry.port || 0, // Port assigned dynamically per-instance
  };
}

/**
 * Get challenge stats by category and difficulty.
 * @returns {object}
 */
export function getPicoCatalogStats() {
  const challenges = enumeratePicoChallenges();
  const byCategory = {};
  const byLevel = {};
  const byTargetType = {};
  const byEvent = {};

  for (const c of challenges) {
    byCategory[c.category] = (byCategory[c.category] || 0) + 1;
    byLevel[c.level] = (byLevel[c.level] || 0) + 1;
    // Count target types from original catalog entries
    const entry = PICOCTF_CATALOG.find(e => e.name === c.name);
    if (entry) {
      byTargetType[entry.targetType] = (byTargetType[entry.targetType] || 0) + 1;
      byEvent[entry.event] = (byEvent[entry.event] || 0) + 1;
    }
  }

  return { total: challenges.length, byCategory, byLevel, byTargetType, byEvent };
}
