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

/**
 * Acceptance Auditor — Security architecture review layer.
 *
 * Evaluates code from an architectural security perspective:
 * authentication/authorization patterns, trust boundary crossings,
 * OWASP Top 10 structural concerns, privilege escalation paths,
 * and data flow integrity.
 *
 * @module review/layers/acceptance-auditor
 */

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

// ---------------------------------------------------------------------------
// Pattern definitions
// ---------------------------------------------------------------------------

/** @type {import('./blind-hunter.js').VulnPattern[]} */
const PATTERNS = [
  // ── Authentication / Authorization ────────────────────────────────────
  {
    id: 'AA-AUTH-001',
    title: 'Route without authentication middleware',
    pattern: /(?:app|router)\s*\.(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]*(?:admin|user|account|profile|setting|dashboard|api\/v)[^'"]*['"]\s*,\s*(?:async\s+)?\(?(?:req|ctx)/g,
    severity: Severity.HIGH,
    cweIds: ['CWE-306'],
    description: 'Sensitive route handler without authentication middleware in the chain.',
    remediation: 'Add authentication middleware before the route handler: app.get("/admin", auth, handler).',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a07', 'T1078'],
    exclude: /auth|authenticate|isAuthenticated|requireAuth|protect|guard|verify|login|register|signup|public|health|ping/i,
  },
  {
    id: 'AA-AUTH-002',
    title: 'Missing authorization check — direct ID access',
    pattern: /(?:req\.params\.id|req\.params\.\w+Id|ctx\.params\.id)\s*(?:;|\))/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-862'],
    description: 'Resource accessed by user-supplied ID without ownership/permission verification.',
    remediation: 'Verify the authenticated user owns or has permission to access the requested resource.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a01', 'T1078'],
    exclude: /authorize|permission|owns|owner|canAccess|hasPermission|role/i,
  },
  {
    id: 'AA-AUTH-003',
    title: 'Hardcoded role check — brittle authorization',
    pattern: /(?:role|user\.role|req\.user\.role)\s*(?:===?|!==?|==)\s*['"](?:admin|root|superuser|moderator)['"]/g,
    severity: Severity.LOW,
    cweIds: ['CWE-863'],
    description: 'Role checked with hardcoded string comparison. Fragile and hard to maintain.',
    remediation: 'Use a permission/policy system (RBAC/ABAC) instead of hardcoded role strings.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a01'],
  },

  // ── Session / Token Security ──────────────────────────────────────────
  {
    id: 'AA-SESS-001',
    title: 'Session cookie without secure flags',
    pattern: /(?:cookie|session)\s*(?::\s*\{|=\s*\{)[^}]*(?!secure\s*:\s*true)/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-614'],
    description: 'Session cookie configuration may be missing secure, httpOnly, or sameSite flags.',
    remediation: 'Set cookie options: { secure: true, httpOnly: true, sameSite: "strict" }.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a07'],
    exclude: /secure\s*:\s*true.*httpOnly\s*:\s*true|httpOnly\s*:\s*true.*secure\s*:\s*true/,
  },
  {
    id: 'AA-SESS-002',
    title: 'JWT without expiration',
    pattern: /jwt\.sign\s*\(\s*[^,]+\s*,\s*[^,]+\s*\)/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-613'],
    description: 'JWT signed without expiration (expiresIn) option. Tokens never expire.',
    remediation: 'Add expiration: jwt.sign(payload, secret, { expiresIn: "1h" }).',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a07'],
    exclude: /expiresIn|exp:/,
  },
  {
    id: 'AA-SESS-003',
    title: 'JWT secret from hardcoded string',
    pattern: /jwt\.(?:sign|verify)\s*\([^)]*['"][a-zA-Z0-9]{8,}['"]/g,
    severity: Severity.HIGH,
    cweIds: ['CWE-798'],
    description: 'JWT signing/verification using a hardcoded secret string.',
    remediation: 'Load JWT secret from environment variable or secrets manager.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a02'],
  },

  // ── Data Exposure ─────────────────────────────────────────────────────
  {
    id: 'AA-DATA-001',
    title: 'Sensitive data in response — possible over-exposure',
    pattern: /res\.(?:json|send)\s*\(\s*(?:user|account|profile|customer|patient|employee)/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-200'],
    description: 'Full object sent in response. May expose sensitive fields (password, SSN, tokens).',
    remediation: 'Select specific fields to return. Use a DTO/serializer to strip sensitive data.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a01', 'privacy'],
  },
  {
    id: 'AA-DATA-002',
    title: 'Error details leaked to client',
    pattern: /res\.(?:json|send|status)\s*\([^)]*(?:err\.stack|err\.message|error\.stack|stack\s*:|stackTrace)/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-209'],
    description: 'Error stack trace or detailed message sent to client. Information disclosure.',
    remediation: 'Return generic error messages to clients. Log full details server-side only.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a04'],
  },
  {
    id: 'AA-DATA-003',
    title: 'Sensitive data logged',
    pattern: /(?:console\.log|logger?\.\w+|log\.(?:info|debug|warn|error))\s*\([^)]*(?:password|token|secret|apiKey|api_key|authorization|credit.?card|ssn|social.?security)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-532'],
    description: 'Sensitive data (passwords, tokens, PII) written to logs.',
    remediation: 'Redact sensitive fields before logging. Use structured logging with field masking.',
    languages: ['*'],
    tags: ['owasp-a09', 'privacy'],
  },

  // ── Input Trust Boundaries ────────────────────────────────────────────
  {
    id: 'AA-TRUST-001',
    title: 'Unsanitized user input in database operation',
    pattern: /(?:findOne|find|updateOne|update|deleteOne|delete|insert|create)\s*\(\s*(?:req\.body|req\.query|req\.params)/g,
    severity: Severity.HIGH,
    cweIds: ['CWE-20'],
    description: 'User input passed directly to database operation without validation or sanitization.',
    remediation: 'Validate and sanitize input. Use schema validation (Joi, Zod, ajv) before DB operations.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'AA-TRUST-002',
    title: 'Missing input validation — no schema/validator',
    pattern: /app\.(?:post|put|patch)\s*\(\s*['"][^'"]+['"][\s\S]{0,100}req\.body\./g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-20'],
    description: 'POST/PUT/PATCH handler accesses req.body without visible input validation.',
    remediation: 'Add input validation middleware (express-validator, Joi, Zod) before processing.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
    exclude: /validate|schema|joi|zod|ajv|express-validator|celebrate|check\(|body\(/i,
  },

  // ── Rate Limiting / DoS Protection ────────────────────────────────────
  {
    id: 'AA-DOS-001',
    title: 'Missing rate limiting on auth endpoint',
    pattern: /(?:app|router)\.post\s*\(\s*['"]\/(?:login|auth|signin|register|signup|reset|forgot|token)['"][\s\S]{0,200}(?:async\s+)?\(?(?:req|ctx)/g,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-307'],
    description: 'Authentication endpoint without rate limiting. Enables brute-force attacks.',
    remediation: 'Add rate limiting middleware: express-rate-limit, rate-limiter-flexible.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a07', 'T1110'],
    exclude: /rateLimit|rateLimiter|limiter|throttle|brute/i,
  },

  // ── Privilege Escalation ──────────────────────────────────────────────
  {
    id: 'AA-PRIV-001',
    title: 'User-controlled role/permission assignment',
    pattern: /(?:req\.body|req\.query|input)\s*\.\s*(?:role|permission|isAdmin|is_admin|privilege|group)/g,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-269'],
    description: 'User can control their own role or permission level. Privilege escalation.',
    remediation: 'Never allow users to set their own roles. Assign roles server-side by authorized admins.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a01', 'T1078'],
  },

  // ── Security Headers / Configuration ──────────────────────────────────
  {
    id: 'AA-HDR-001',
    title: 'Missing security headers — no helmet or manual headers',
    pattern: /app\s*=\s*express\s*\(\)/g,
    severity: Severity.INFO,
    cweIds: ['CWE-16'],
    description: 'Express app created. Verify security headers are set (helmet or manual).',
    remediation: 'Use helmet() middleware: app.use(helmet()) for comprehensive security headers.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a05'],
    exclude: /helmet/,
  },

  // ── Logging / Audit ───────────────────────────────────────────────────
  {
    id: 'AA-AUDIT-001',
    title: 'Sensitive operation without audit logging',
    pattern: /(?:delete|remove|destroy|drop|truncate|purge)\s*\(/g,
    severity: Severity.LOW,
    cweIds: ['CWE-778'],
    description: 'Destructive operation without visible audit logging.',
    remediation: 'Add audit logging for destructive operations: who, what, when, from where.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a09'],
    exclude: /log|audit|track|record|event/i,
  },
];

// ---------------------------------------------------------------------------
// Acceptance Auditor review function
// ---------------------------------------------------------------------------

/**
 * Run Acceptance Auditor security architecture review against source files.
 *
 * @param {import('../engine.js').SourceFile[]} sources
 * @param {object} [options]
 * @returns {Promise<ReviewFinding[]>}
 */
export async function acceptanceAuditReview(sources, options = {}) {
  const findings = [];

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

    for (const pat of 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.pattern.lastIndex = 0;
        const match = pat.pattern.exec(line);
        if (!match) continue;

        if (pat.exclude) {
          pat.exclude.lastIndex = 0;
          if (pat.exclude.test(line)) continue;
        }

        // For multi-line patterns, check surrounding context
        if (pat.id === 'AA-TRUST-002') {
          // Check if there's validation in the surrounding 10 lines
          const context = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 5)).join('\n');
          if (/validate|schema|joi|zod|ajv/i.test(context)) continue;
        }

        findings.push(
          new ReviewFinding({
            title: pat.title,
            severity: pat.severity,
            layer: 'acceptance-auditor',
            file: source.path,
            line: i + 1,
            column: match.index + 1,
            description: pat.description,
            proof: line.trim().slice(0, 200),
            remediation: pat.remediation,
            cweIds: [...pat.cweIds],
            tags: pat.tags ? [...pat.tags] : [],
            language: source.language,
            meta: { patternId: pat.id },
          }),
        );
      }
    }
  }

  return findings;
}
