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

/**
 * Defense-in-Depth Validation Layer
 *
 * Detects functions/endpoints relying on single-layer security validation.
 * Flags code that has one security control where multiple are expected.
 *
 * Examples:
 * - Auth check without input validation
 * - SQL parameterization without output encoding
 * - File upload without type AND size validation
 * - API endpoint without both auth AND rate limiting
 *
 * @module review/layers/defense-in-depth
 */

import { ReviewFinding, Severity } from '../engine.js';

// ---------------------------------------------------------------------------
// Defense-in-depth patterns
// ---------------------------------------------------------------------------

/**
 * Each pattern looks for the ABSENCE of a companion control.
 * Structure: {presence, absence, ...} — if presence matches but absence doesn't,
 * it's a defense-in-depth gap.
 *
 * @typedef {object} DepthPattern
 * @property {string} id
 * @property {string} title
 * @property {RegExp} presence     - Pattern that MUST be present (the single control)
 * @property {RegExp} absence      - Companion control that SHOULD be present nearby
 * @property {number} searchRadius - Lines around the presence match to search for absence
 * @property {string} severity
 * @property {string[]} cweIds
 * @property {string} description
 * @property {string} remediation
 * @property {string[]} languages
 * @property {string} presentLayer
 * @property {string} missingLayer
 */

/** @type {DepthPattern[]} */
const DEPTH_PATTERNS = [
  {
    id: 'DID-001',
    title: 'Auth without input validation',
    presence: /(?:isAuthenticated|requireAuth|auth|authenticate|verifyToken|passport\.authenticate)\s*[\(,)]/gi,
    absence: /(?:validate|schema|joi|zod|ajv|express-validator|celebrate|sanitize|check\(|body\()/i,
    searchRadius: 15,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-20'],
    description: 'Authentication is present but input validation is missing. Authenticated users can still send malicious input.',
    remediation: 'Add input validation (Joi, Zod, express-validator) alongside authentication middleware.',
    languages: ['javascript', 'typescript'],
    presentLayer: 'authentication',
    missingLayer: 'input validation',
  },
  {
    id: 'DID-002',
    title: 'Input validation without output encoding',
    presence: /(?:validate|schema|joi|zod|ajv|express-validator|celebrate|sanitize)/gi,
    absence: /(?:escape|encode|htmlEntities|DOMPurify|sanitizeHtml|textContent|createTextNode)/i,
    searchRadius: 20,
    severity: Severity.LOW,
    cweIds: ['CWE-116'],
    description: 'Input validation present but output encoding is missing. Validation alone does not prevent stored XSS.',
    remediation: 'Add output encoding/escaping when rendering user-supplied data.',
    languages: ['javascript', 'typescript'],
    presentLayer: 'input validation',
    missingLayer: 'output encoding',
  },
  {
    id: 'DID-003',
    title: 'File upload without type AND size validation',
    presence: /(?:multer|upload|formidable|busboy|multipart)/gi,
    absence: /(?:fileFilter|limits|maxFileSize|allowedTypes|mimetype|content-type.*check|fileSize)/i,
    searchRadius: 10,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-434'],
    description: 'File upload handler present but missing file type or size validation. Unrestricted upload enables web shell deployment.',
    remediation: 'Add file type whitelist (by extension AND MIME type) and maximum file size limit.',
    languages: ['javascript', 'typescript', 'python'],
    presentLayer: 'file upload handling',
    missingLayer: 'file type/size validation',
  },
  {
    id: 'DID-004',
    title: 'API endpoint without rate limiting',
    presence: /(?:app|router)\.(?:post|put|patch|delete)\s*\(/gi,
    absence: /(?:rateLimit|rateLimiter|limiter|throttle|slowDown|express-rate-limit)/i,
    searchRadius: 5,
    severity: Severity.LOW,
    cweIds: ['CWE-770'],
    description: 'Write endpoint without rate limiting. Enables abuse, resource exhaustion, and brute-force attacks.',
    remediation: 'Add rate limiting middleware to mutation endpoints.',
    languages: ['javascript', 'typescript'],
    presentLayer: 'endpoint handler',
    missingLayer: 'rate limiting',
  },
  {
    id: 'DID-005',
    title: 'Database operation without audit logging',
    presence: /(?:\.(?:update|delete|remove|destroy|create|insert|save))\s*\(/gi,
    absence: /(?:audit|log\.(?:info|warn)|logger|track|event|record|history)/i,
    searchRadius: 8,
    severity: Severity.LOW,
    cweIds: ['CWE-778'],
    description: 'Database mutation without audit logging. Changes cannot be traced to actors during incident response.',
    remediation: 'Add audit logging capturing who, what, when for all data mutations.',
    languages: ['javascript', 'typescript', 'python'],
    presentLayer: 'data mutation',
    missingLayer: 'audit logging',
  },
  {
    id: 'DID-006',
    title: 'Crypto operation without key management',
    presence: /(?:createCipher|createSign|createHmac|encrypt|decrypt)\s*\(/gi,
    absence: /(?:keyRotat|kms|vault|secretManager|keyManag|KEY_VERSION|rotateKey|getKey\()/i,
    searchRadius: 20,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-320'],
    description: 'Cryptographic operation without key management/rotation mechanism. Static keys increase exposure window.',
    remediation: 'Implement key rotation policy. Use a KMS, Vault, or at minimum versioned key references.',
    languages: ['javascript', 'typescript', 'python'],
    presentLayer: 'cryptographic operation',
    missingLayer: 'key management/rotation',
  },
  {
    id: 'DID-007',
    title: 'Session management without CSRF protection',
    presence: /(?:session|cookie|express-session|cookie-session)\s*[\(\{]/gi,
    absence: /(?:csrf|csurf|csrfToken|_csrf|xsrf|sameSite.*(?:strict|lax))/i,
    searchRadius: 15,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-352'],
    description: 'Session management without CSRF protection. Session-based auth is vulnerable to cross-site request forgery.',
    remediation: 'Add CSRF token validation or set SameSite cookie attribute to Strict/Lax.',
    languages: ['javascript', 'typescript'],
    presentLayer: 'session management',
    missingLayer: 'CSRF protection',
  },
];

// ---------------------------------------------------------------------------
// Defense-in-Depth review function
// ---------------------------------------------------------------------------

/**
 * Run defense-in-depth validation against source files.
 *
 * @param {import('../engine.js').SourceFile[]} sources
 * @param {object} [options]
 * @returns {Promise<ReviewFinding[]>}
 */
export async function defenseInDepthReview(sources, options = {}) {
  const findings = [];

  for (const source of sources) {
    const lines = source.content.split('\n');

    for (const pat of DEPTH_PATTERNS) {
      if (!pat.languages.includes('*') && !pat.languages.includes(source.language)) {
        continue;
      }

      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const trimmed = line.trimStart();
        if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;

        pat.presence.lastIndex = 0;
        const match = pat.presence.exec(line);
        if (!match) continue;

        // Search surrounding lines for the companion control
        const start = Math.max(0, i - pat.searchRadius);
        const end = Math.min(lines.length, i + pat.searchRadius + 1);
        const context = lines.slice(start, end).join('\n');

        pat.absence.lastIndex = 0;
        if (pat.absence.test(context)) continue; // Companion found — no gap

        findings.push(
          new ReviewFinding({
            title: pat.title,
            severity: pat.severity,
            layer: 'defense-in-depth',
            file: source.path,
            line: i + 1,
            column: match.index + 1,
            description: `${pat.description}\n  Present: ${pat.presentLayer}\n  Missing: ${pat.missingLayer}`,
            proof: line.trim().slice(0, 200),
            remediation: pat.remediation,
            cweIds: [...pat.cweIds],
            tags: ['defense-in-depth'],
            language: source.language,
            meta: { patternId: pat.id, presentLayer: pat.presentLayer, missingLayer: pat.missingLayer },
          }),
        );
      }
    }
  }

  return findings;
}
