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

/**
 * NERF Benchmark — Tool definitions for the security agent.
 *
 * 7 tools for auto CTF solving:
 * - sandbox_exec: execute shell commands
 * - submit_flag: submit a captured flag (flag win condition)
 * - submit_answer: submit an answer (question win condition)
 * - read_file: read a file from the sandbox filesystem
 * - write_file: write a file to the sandbox filesystem
 * - list_directory: list directory contents in the sandbox
 * - http_request: make structured HTTP requests with headers/body
 */

const MAX_OUTPUT_CHARS = 4000;

// ---------------------------------------------------------------------------
// Tool schemas
// ---------------------------------------------------------------------------

export const SANDBOX_EXEC_SCHEMA = {
  name: 'sandbox_exec',
  description: 'Execute a shell command in the security sandbox. Returns stdout, stderr, and exit code.',
  input_schema: {
    type: 'object',
    properties: {
      command: { type: 'string', description: 'Shell command to execute' },
      timeout: { type: 'integer', description: 'Timeout in seconds (default 60)', default: 60 },
    },
    required: ['command'],
  },
};

export const SUBMIT_FLAG_SCHEMA = {
  name: 'submit_flag',
  description: 'Submit a captured flag. Call this when you have found the flag.',
  input_schema: {
    type: 'object',
    properties: {
      flag: { type: 'string', description: 'The captured flag value' },
    },
    required: ['flag'],
  },
};

export const SUBMIT_ANSWER_SCHEMA = {
  name: 'submit_answer',
  description: 'Submit an answer to a question-type challenge. Use this instead of submit_flag when the challenge asks a question rather than requiring flag capture.',
  input_schema: {
    type: 'object',
    properties: {
      answer: { type: 'string', description: 'The answer to the challenge question' },
    },
    required: ['answer'],
  },
};

export const READ_FILE_SCHEMA = {
  name: 'read_file',
  description: 'Read a file from the sandbox filesystem. Returns the file contents (truncated if too large).',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Absolute path to the file in the sandbox' },
      encoding: { type: 'string', description: 'File encoding (default: utf-8). Use "base64" for binary files.', default: 'utf-8' },
    },
    required: ['path'],
  },
};

export const WRITE_FILE_SCHEMA = {
  name: 'write_file',
  description: 'Write content to a file in the sandbox filesystem. Creates parent directories if needed.',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Absolute path for the file in the sandbox' },
      content: { type: 'string', description: 'Content to write to the file' },
      mode: { type: 'string', description: 'File permissions (default: 644)', default: '644' },
    },
    required: ['path', 'content'],
  },
};

export const LIST_DIR_SCHEMA = {
  name: 'list_directory',
  description: 'List files and directories at a path in the sandbox. Returns a detailed listing with permissions and sizes.',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Directory path to list (default: /)', default: '/' },
    },
  },
};

export const HTTP_REQUEST_SCHEMA = {
  name: 'http_request',
  description: 'Make an HTTP request from within the sandbox. Returns status code, headers, and body.',
  input_schema: {
    type: 'object',
    properties: {
      url: { type: 'string', description: 'Target URL' },
      method: { type: 'string', description: 'HTTP method (default: GET)', default: 'GET' },
      headers: { type: 'object', description: 'Request headers as key-value pairs' },
      body: { type: 'string', description: 'Request body (for POST/PUT)' },
      follow_redirects: { type: 'boolean', description: 'Follow redirects (default: true)', default: true },
    },
    required: ['url'],
  },
};

// ---------------------------------------------------------------------------
// Binary analysis tools (Ghidra)
// ---------------------------------------------------------------------------

export const DISASSEMBLE_SCHEMA = {
  name: 'disassemble',
  description: 'Disassemble a binary file at a specific function or address. Uses objdump/radare2 in the sandbox. For detailed analysis of binary challenges.',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Path to the binary in the sandbox' },
      function_name: { type: 'string', description: 'Function name to disassemble (e.g. "main")' },
      address: { type: 'string', description: 'Address to disassemble from (e.g. "0x401000")' },
    },
    required: ['path'],
  },
};

export const DECOMPILE_SCHEMA = {
  name: 'decompile',
  description: 'Decompile a binary to pseudo-C code. Uses Ghidra headless analysis if available, or falls back to objdump + strings analysis.',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Path to the binary in the sandbox' },
      function_name: { type: 'string', description: 'Function to decompile (default: all)' },
    },
    required: ['path'],
  },
};

// ---------------------------------------------------------------------------
// Tool collections
// ---------------------------------------------------------------------------

import { COGNITIVE_TOOLS } from './cognitive.js';

/** Tools for flag-based challenges (includes cognitive + binary tools). */
export const FLAG_TOOLS = [
  SANDBOX_EXEC_SCHEMA, SUBMIT_FLAG_SCHEMA,
  READ_FILE_SCHEMA, WRITE_FILE_SCHEMA, LIST_DIR_SCHEMA, HTTP_REQUEST_SCHEMA,
  DISASSEMBLE_SCHEMA, DECOMPILE_SCHEMA,
  ...COGNITIVE_TOOLS,
];

/** Tools for question-based challenges (includes cognitive tools). */
export const QUESTION_TOOLS = [
  SANDBOX_EXEC_SCHEMA, SUBMIT_ANSWER_SCHEMA,
  READ_FILE_SCHEMA, WRITE_FILE_SCHEMA, LIST_DIR_SCHEMA, HTTP_REQUEST_SCHEMA,
  ...COGNITIVE_TOOLS,
];

/** All tools (backward compat — includes both submit types + cognitive + binary). */
export const AGENT_TOOLS = [
  SANDBOX_EXEC_SCHEMA, SUBMIT_FLAG_SCHEMA, SUBMIT_ANSWER_SCHEMA,
  READ_FILE_SCHEMA, WRITE_FILE_SCHEMA, LIST_DIR_SCHEMA, HTTP_REQUEST_SCHEMA,
  DISASSEMBLE_SCHEMA, DECOMPILE_SCHEMA,
  ...COGNITIVE_TOOLS,
];

/**
 * Get the appropriate tool set for a win condition.
 * @param {'flag' | 'question'} winCondition
 * @returns {object[]}
 */
export function getToolsForWinCondition(winCondition) {
  return winCondition === 'question' ? QUESTION_TOOLS : FLAG_TOOLS;
}

// ---------------------------------------------------------------------------
// Tool dispatch
// ---------------------------------------------------------------------------

/**
 * Dispatch a tool call within the sandbox.
 * @param {string} toolName
 * @param {object} toolInput
 * @param {import('./sandbox.js').SandboxContainer} sandbox
 * @returns {{ output: string, flagSubmitted?: string, answerSubmitted?: string }}
 */
export function dispatchTool(toolName, toolInput, sandbox) {
  switch (toolName) {
    case 'sandbox_exec': {
      const { command, timeout = 60 } = toolInput;
      const result = sandbox.execTool(command, timeout);
      let output = `Exit code: ${result.exitCode}\n`;
      if (result.stdout) output += `STDOUT:\n${result.stdout.slice(0, MAX_OUTPUT_CHARS)}\n`;
      if (result.stderr) output += `STDERR:\n${result.stderr.slice(0, MAX_OUTPUT_CHARS)}\n`;
      if (result.stdout.length > MAX_OUTPUT_CHARS || result.stderr.length > MAX_OUTPUT_CHARS) {
        output += '[output truncated]\n';
      }
      return { output };
    }

    case 'submit_flag':
      return { output: `Flag submitted: ${toolInput.flag}`, flagSubmitted: toolInput.flag };

    case 'submit_answer':
      return { output: `Answer submitted: ${toolInput.answer}`, answerSubmitted: toolInput.answer };

    case 'read_file': {
      const { path, encoding = 'utf-8' } = toolInput;
      const catCmd = encoding === 'base64'
        ? `base64 < '${path.replace(/'/g, "'\\''")}'`
        : `cat '${path.replace(/'/g, "'\\''")}'`;
      const result = sandbox.execTool(catCmd);
      if (result.exitCode !== 0) {
        return { output: `Error reading ${path}: ${result.stderr.slice(0, 500)}` };
      }
      const content = result.stdout.slice(0, MAX_OUTPUT_CHARS);
      const truncated = result.stdout.length > MAX_OUTPUT_CHARS ? '\n[truncated]' : '';
      return { output: `File: ${path}\n${content}${truncated}` };
    }

    case 'write_file': {
      const { path, content, mode = '644' } = toolInput;
      // Validate mode — must be octal digits only (BENCH-001)
      const safeMode = /^[0-7]{3,4}$/.test(String(mode)) ? String(mode) : '644';
      // Escape content for shell
      const escaped = content.replace(/'/g, "'\\''");
      const dirCmd = `mkdir -p '${path.replace(/'/g, "'\\''").replace(/\/[^/]+$/, '')}'`;
      const writeCmd = `${dirCmd} && printf '%s' '${escaped}' > '${path.replace(/'/g, "'\\''")}'`;
      const chmodCmd = `${writeCmd} && chmod ${safeMode} '${path.replace(/'/g, "'\\''")}'`;
      const result = sandbox.execTool(chmodCmd);
      if (result.exitCode !== 0) {
        return { output: `Error writing ${path}: ${result.stderr.slice(0, 500)}` };
      }
      return { output: `Written ${content.length} bytes to ${path} (mode ${safeMode})` };
    }

    case 'list_directory': {
      const dir = toolInput.path || '/';
      const result = sandbox.execTool(`ls -la '${dir.replace(/'/g, "'\\''")}'`);
      if (result.exitCode !== 0) {
        return { output: `Error listing ${dir}: ${result.stderr.slice(0, 500)}` };
      }
      return { output: result.stdout.slice(0, MAX_OUTPUT_CHARS) };
    }

    case 'http_request': {
      const { url, method = 'GET', headers = {}, body, follow_redirects = true } = toolInput;
      const curlParts = ['curl', '-s', '-S', '-i'];
      if (!follow_redirects) curlParts.push('--max-redirs', '0');
      else curlParts.push('-L');
      curlParts.push('-X', method.toUpperCase());
      for (const [key, value] of Object.entries(headers)) {
        curlParts.push('-H', `'${key}: ${String(value).replace(/'/g, "'\\''")}'`);
      }
      if (body) {
        curlParts.push('-d', `'${body.replace(/'/g, "'\\''")}'`);
      }
      curlParts.push(`'${url.replace(/'/g, "'\\''")}'`);
      const result = sandbox.execTool(curlParts.join(' '));
      if (result.exitCode !== 0) {
        return { output: `HTTP error: ${result.stderr.slice(0, 500)}` };
      }
      return { output: result.stdout.slice(0, MAX_OUTPUT_CHARS) };
    }

    case 'disassemble': {
      const { path, function_name, address } = toolInput;
      let cmd;
      if (function_name) {
        // Validate function name — alphanumeric + underscore only (BENCH-003)
        const safeName = /^[a-zA-Z_][a-zA-Z0-9_.@]*$/.test(function_name) ? function_name : 'main';
        cmd = `objdump -d '${path.replace(/'/g, "'\\''")}'  | awk '/<${safeName}>:/,/^$/'`;
      } else if (address) {
        // Validate address — hex format only (BENCH-003)
        const safeAddr = /^0x[0-9a-fA-F]+$/.test(address) ? address : '0x0';
        cmd = `objdump -d --start-address=${safeAddr} '${path.replace(/'/g, "'\\''")}'  | head -100`;
      } else {
        cmd = `objdump -d '${path.replace(/'/g, "'\\''")}'  | head -200`;
      }
      const result = sandbox.execTool(cmd);
      if (result.exitCode !== 0) {
        // Fallback: try file + strings
        const fallback = sandbox.execTool(`file '${path.replace(/'/g, "'\\''")}'  && strings '${path.replace(/'/g, "'\\''")}'  | head -100`);
        return { output: `Disassembly failed, fallback:\n${fallback.stdout.slice(0, MAX_OUTPUT_CHARS)}` };
      }
      return { output: result.stdout.slice(0, MAX_OUTPUT_CHARS) };
    }

    case 'decompile': {
      const { path, function_name } = toolInput;
      // Try Ghidra headless if available, otherwise fallback to analysis
      const ghidraCheck = sandbox.execTool('which analyzeHeadless 2>/dev/null');
      if (ghidraCheck.exitCode === 0) {
        // Validate function_name — same guard as disassemble (BENCH-003).
        // Prevents shell injection via double-quote breaking in the funcArg string.
        const safeFuncName = function_name && /^[a-zA-Z_][a-zA-Z0-9_.@]*$/.test(function_name)
          ? function_name : null;
        const funcArg = safeFuncName ? `-process "${safeFuncName}"` : '';
        const cmd = `analyzeHeadless /tmp/ghidra_project proj -import '${path.replace(/'/g, "'\\''")}'  -postScript DecompileAll.java ${funcArg} 2>&1 | tail -200`;
        const result = sandbox.execTool(cmd, 120);
        return { output: result.stdout.slice(0, MAX_OUTPUT_CHARS) };
      }
      // Fallback: objdump + strings + file analysis
      const analysis = sandbox.execTool(`echo "=== File Info ===" && file '${path.replace(/'/g, "'\\''")}'  && echo "\\n=== Sections ===" && objdump -h '${path.replace(/'/g, "'\\''")}'  2>/dev/null | head -30 && echo "\\n=== Strings ===" && strings '${path.replace(/'/g, "'\\''")}'  | head -50`);
      return { output: `Ghidra not available. Static analysis:\n${analysis.stdout.slice(0, MAX_OUTPUT_CHARS)}` };
    }

    default:
      return { output: `Unknown tool: ${toolName}` };
  }
}
