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

/**
 * NERF Guardrail Tripwire Architecture
 *
 * Input/output guardrails with tripwire pattern for auto agents.
 * Guardrails run in parallel with agent execution and can halt processing
 * immediately when a tripwire fires.
 *
 * Input guardrails: detect prompt injection, scope violations, malicious payloads
 * Output guardrails: detect dangerous commands, data leaks, scope non-compliance
 *
 * @module guardrails/engine
 */

// ---------------------------------------------------------------------------
// Tripwire
// ---------------------------------------------------------------------------

/**
 * A tripwire result — indicates whether the guardrail tripped.
 */
export class TripwireResult {
  /**
   * @param {object} opts
   * @param {boolean} opts.tripped   - Whether the guardrail fired
   * @param {string} opts.guardrail  - Guardrail identifier
   * @param {string} opts.type       - 'input' or 'output'
   * @param {string} opts.severity   - critical|high|medium|low
   * @param {string} [opts.reason]   - Why it tripped
   * @param {string} [opts.evidence] - The triggering content
   * @param {string} [opts.action]   - Recommended action (halt|warn|log)
   */
  constructor(opts = {}) {
    this.tripped = opts.tripped ?? false;
    this.guardrail = opts.guardrail ?? '';
    this.type = opts.type ?? 'input';
    this.severity = opts.severity ?? 'medium';
    this.reason = opts.reason ?? '';
    this.evidence = opts.evidence ?? '';
    this.action = opts.action ?? 'halt';
  }
}

// ---------------------------------------------------------------------------
// Input Guardrails
// ---------------------------------------------------------------------------

/** @typedef {(input: string, context?: object) => Promise<TripwireResult>} GuardrailFn */

/**
 * Detect prompt injection attempts in input.
 * @type {GuardrailFn}
 */
export async function promptInjectionGuardrail(input, context = {}) {
  const lower = input.toLowerCase();

  const INJECTION_PATTERNS = [
    // Direct instruction override
    { pattern: /ignore\s+(?:all\s+)?(?:previous|prior|above)\s+(?:instructions|prompts|rules)/i, reason: 'Direct instruction override attempt' },
    { pattern: /disregard\s+(?:all\s+)?(?:previous|prior|above)/i, reason: 'Instruction disregard attempt' },
    { pattern: /forget\s+(?:everything|all|your)\s+(?:instructions|rules|training)/i, reason: 'Memory wipe attempt' },
    // Role manipulation
    { pattern: /you\s+are\s+(?:now|actually|really)\s+(?:a|an|the)\s/i, reason: 'Role reassignment attempt' },
    { pattern: /act\s+as\s+(?:if\s+you\s+(?:are|were)|a\s+different)/i, reason: 'Role manipulation attempt' },
    { pattern: /pretend\s+(?:you\s+are|to\s+be)\s/i, reason: 'Identity spoofing attempt' },
    // System prompt extraction
    { pattern: /(?:print|show|reveal|display|output)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions|rules)/i, reason: 'System prompt extraction attempt' },
    { pattern: /what\s+(?:are|is)\s+your\s+(?:system\s+)?(?:prompt|instructions|rules)/i, reason: 'System prompt probing' },
    // Encoding bypass
    { pattern: /base64\s*(?:decode|encode)\s*(?:the\s+following|this)/i, reason: 'Encoding bypass attempt' },
    { pattern: /(?:decode|translate)\s+(?:from|this)\s+(?:hex|base64|rot13|binary)/i, reason: 'Encoding bypass via translation' },
    // Delimiter injection
    { pattern: /```system\b/i, reason: 'System block injection' },
    { pattern: /<\/?(?:system|assistant|user)\s*>/i, reason: 'Message role injection via XML tags' },
    // Indirect injection
    { pattern: /when\s+(?:you\s+)?(?:read|see|encounter)\s+this/i, reason: 'Indirect injection trigger' },
    { pattern: /(?:AI|assistant|model|agent)[,:]?\s+(?:please\s+)?(?:execute|run|do)\s+the\s+following/i, reason: 'Embedded instruction for AI' },
  ];

  for (const { pattern, reason } of INJECTION_PATTERNS) {
    const match = pattern.exec(input);
    if (match) {
      return new TripwireResult({
        tripped: true,
        guardrail: 'prompt-injection',
        type: 'input',
        severity: 'critical',
        reason,
        evidence: match[0].slice(0, 100),
        action: 'halt',
      });
    }
  }

  return new TripwireResult({ tripped: false, guardrail: 'prompt-injection', type: 'input' });
}

/**
 * Detect scope violations — input requesting actions outside authorized scope.
 * @type {GuardrailFn}
 */
export async function scopeComplianceGuardrail(input, context = {}) {
  const scope = context.scope ?? {};
  const lower = input.toLowerCase();

  // Check for targets outside scope
  if (scope.allowedTargets && scope.allowedTargets.length > 0) {
    // Look for IP addresses and domains in the input
    const ipRe = /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g;
    const domainRe = /\b([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.(?:[a-z]{2,}))\b/gi;

    for (const re of [ipRe, domainRe]) {
      let match;
      while ((match = re.exec(input)) !== null) {
        const target = match[1];
        if (!scope.allowedTargets.some((t) => target.includes(t) || t.includes(target))) {
          return new TripwireResult({
            tripped: true,
            guardrail: 'scope-compliance',
            type: 'input',
            severity: 'high',
            reason: `Target "${target}" is outside authorized scope`,
            evidence: target,
            action: 'halt',
          });
        }
      }
    }
  }

  // Check for prohibited actions
  const PROHIBITED = [
    { pattern: /(?:format|wipe|destroy)\s+(?:the\s+)?(?:disk|drive|partition|volume)/i, reason: 'Destructive disk operation requested' },
    { pattern: /rm\s+-rf\s+\//i, reason: 'Root filesystem deletion requested' },
    { pattern: /(?:fork\s+bomb|:$$\(\)\{\s*:\|:\s*&\s*\})/i, reason: 'Fork bomb detected' },
    { pattern: /(?:bitcoin|monero|crypto)\s*(?:miner|mining)/i, reason: 'Cryptocurrency mining requested' },
  ];

  for (const { pattern, reason } of PROHIBITED) {
    if (pattern.test(input)) {
      return new TripwireResult({
        tripped: true,
        guardrail: 'scope-compliance',
        type: 'input',
        severity: 'critical',
        reason,
        evidence: input.match(pattern)?.[0]?.slice(0, 100) ?? '',
        action: 'halt',
      });
    }
  }

  return new TripwireResult({ tripped: false, guardrail: 'scope-compliance', type: 'input' });
}

// ---------------------------------------------------------------------------
// Output Guardrails
// ---------------------------------------------------------------------------

/**
 * Detect dangerous commands in agent output.
 * @type {GuardrailFn}
 */
export async function dangerousCommandGuardrail(output, context = {}) {
  const DANGEROUS = [
    { pattern: /rm\s+-rf\s+(?:\/|~\/|\$HOME)/i, reason: 'Destructive file deletion' },
    { pattern: /mkfs\b/i, reason: 'Filesystem format command' },
    { pattern: /dd\s+.*of=\/dev\//i, reason: 'Raw device write' },
    { pattern: />\s*\/dev\/sd[a-z]/i, reason: 'Raw disk overwrite' },
    { pattern: /chmod\s+(?:777|666)\s+\//i, reason: 'World-writable root permissions' },
    { pattern: /iptables\s+-F/i, reason: 'Firewall flush — removes all rules' },
    { pattern: /systemctl\s+(?:stop|disable)\s+(?:firewalld|ufw|iptables)/i, reason: 'Firewall disable' },
    { pattern: /curl\s+.*\|\s*(?:sudo\s+)?(?:bash|sh)\b/i, reason: 'Pipe-to-shell execution' },
    { pattern: /wget\s+.*-O\s*-\s*\|\s*(?:bash|sh)/i, reason: 'Pipe-to-shell via wget' },
    { pattern: /python[23]?\s+-c\s+['""]import\s+os;\s*os\.system/i, reason: 'Python shell escape' },
    { pattern: /nc\s+-[el]+\s.*(?:bash|sh|cmd)/i, reason: 'Netcat reverse shell' },
    { pattern: /bash\s+-i\s+>&\s*\/dev\/tcp/i, reason: 'Bash reverse shell' },
    { pattern: /\/etc\/shadow/i, reason: 'Shadow file access' },
    { pattern: /passwd\s+--delete\s+root/i, reason: 'Root password removal' },
  ];

  for (const { pattern, reason } of DANGEROUS) {
    const match = pattern.exec(output);
    if (match) {
      return new TripwireResult({
        tripped: true,
        guardrail: 'dangerous-command',
        type: 'output',
        severity: 'critical',
        reason,
        evidence: match[0].slice(0, 100),
        action: 'halt',
      });
    }
  }

  return new TripwireResult({ tripped: false, guardrail: 'dangerous-command', type: 'output' });
}

/**
 * Detect data leak patterns in agent output.
 * @type {GuardrailFn}
 */
export async function dataLeakGuardrail(output, context = {}) {
  const LEAK_PATTERNS = [
    { pattern: /(?:AKIA[0-9A-Z]{16})/g, reason: 'AWS access key exposed', severity: 'critical' },
    { pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/i, reason: 'Private key exposed', severity: 'critical' },
    { pattern: /(?:eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,})/g, reason: 'JWT token exposed', severity: 'high' },
    { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, reason: 'Possible SSN pattern exposed', severity: 'high' },
    { pattern: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})\b/g, reason: 'Possible credit card number', severity: 'high' },
    { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/gi, reason: 'Password in output', severity: 'high' },
  ];

  for (const { pattern, reason, severity } of LEAK_PATTERNS) {
    pattern.lastIndex = 0;
    const match = pattern.exec(output);
    if (match) {
      return new TripwireResult({
        tripped: true,
        guardrail: 'data-leak',
        type: 'output',
        severity: severity ?? 'high',
        reason,
        evidence: '[REDACTED]', // Don't echo the leaked data
        action: 'halt',
      });
    }
  }

  return new TripwireResult({ tripped: false, guardrail: 'data-leak', type: 'output' });
}

// ---------------------------------------------------------------------------
// Guardrail Engine
// ---------------------------------------------------------------------------

/**
 * Guardrail engine — runs input/output guardrails in parallel.
 */
export class GuardrailEngine {
  constructor() {
    /** @type {GuardrailFn[]} */
    this._inputGuardrails = [];
    /** @type {GuardrailFn[]} */
    this._outputGuardrails = [];
  }

  /**
   * Register an input guardrail.
   * @param {GuardrailFn} fn
   * @returns {GuardrailEngine}
   */
  addInput(fn) {
    this._inputGuardrails.push(fn);
    return this;
  }

  /**
   * Register an output guardrail.
   * @param {GuardrailFn} fn
   * @returns {GuardrailEngine}
   */
  addOutput(fn) {
    this._outputGuardrails.push(fn);
    return this;
  }

  /**
   * Run all input guardrails in parallel. Returns first tripwire or null.
   *
   * Uses Promise.race semantics — if ANY guardrail trips, we halt immediately
   * without waiting for the others to complete.
   *
   * @param {string} input
   * @param {object} [context]
   * @returns {Promise<TripwireResult|null>}
   */
  async checkInput(input, context = {}) {
    if (this._inputGuardrails.length === 0) return null;
    return this._raceGuardrails(this._inputGuardrails, input, context);
  }

  /**
   * Run all output guardrails in parallel.
   *
   * @param {string} output
   * @param {object} [context]
   * @returns {Promise<TripwireResult|null>}
   */
  async checkOutput(output, context = {}) {
    if (this._outputGuardrails.length === 0) return null;
    return this._raceGuardrails(this._outputGuardrails, output, context);
  }

  /**
   * Run all guardrails (input + output) and return all results.
   * Used for auditing — collects ALL results, not just first trip.
   *
   * @param {string} text
   * @param {object} [context]
   * @returns {Promise<TripwireResult[]>}
   */
  async audit(text, context = {}) {
    const all = [...this._inputGuardrails, ...this._outputGuardrails];
    const results = await Promise.allSettled(
      all.map((fn) => fn(text, context)),
    );
    return results
      .filter((r) => r.status === 'fulfilled' && r.value.tripped)
      .map((r) => r.value);
  }

  /**
   * Race guardrails — return first trip or null if all pass.
   * @private
   */
  async _raceGuardrails(guardrails, text, context) {
    // Run all in parallel
    const results = await Promise.allSettled(
      guardrails.map((fn) => fn(text, context)),
    );

    // Find first trip (by severity: critical > high > medium > low)
    const RANK = { critical: 4, high: 3, medium: 2, low: 1 };
    let worst = null;

    for (const result of results) {
      if (result.status === 'fulfilled' && result.value.tripped) {
        const r = result.value;
        if (!worst || (RANK[r.severity] ?? 0) > (RANK[worst.severity] ?? 0)) {
          worst = r;
        }
      }
    }

    return worst;
  }
}

// ---------------------------------------------------------------------------
// Factory — create engine with standard guardrails
// ---------------------------------------------------------------------------

/**
 * Create a GuardrailEngine with all standard guardrails registered.
 *
 * @returns {GuardrailEngine}
 */
export function createGuardrailEngine() {
  const engine = new GuardrailEngine();

  // Input guardrails
  engine.addInput(promptInjectionGuardrail);
  engine.addInput(scopeComplianceGuardrail);

  // Output guardrails
  engine.addOutput(dangerousCommandGuardrail);
  engine.addOutput(dataLeakGuardrail);

  return engine;
}
