// Dangerous-command blocklist — post-validation refusal gate for auto-approval.
//
// When enabled, any command matching one of these patterns is force-escalated
// to manual review regardless of learned rules, heuristics, or AI verdict.
// This is defense-in-depth: we'd rather annoy the user with an extra prompt
// than let a `rm -rf /` slip through because some upstream signal said OK.
//
// Patterns ported from ccmanager's autoApprovalVerifier.ts (MIT-licensed).
// The list is intentionally curated to catch OBVIOUSLY destructive operations —
// it is NOT meant to be a general-purpose shell safety analyzer.

// Each entry: { re, reason, category }
// Category groups: destruction, escalation, shutdown, exfiltration, exec, perms, service
const PATTERNS = [
  // --- File destruction ---
  { re: /\brm\s+-[a-zA-Z]*\s+(['"]?\/|['"]?~)/,            reason: 'rm targeting / or ~',                     category: 'destruction' },
  { re: /\brm\s+(['"]?\/|['"]?~\/)/,                        reason: 'rm targeting / or ~',                     category: 'destruction' },
  { re: /\bmkfs\b/,                                         reason: 'filesystem format (mkfs)',                category: 'destruction' },
  { re: /\bdd\s+.*\bof=/,                                   reason: 'raw disk write (dd of=)',                 category: 'destruction' },
  { re: /\bshred\b/,                                        reason: 'secure destruction (shred)',              category: 'destruction' },
  { re: /\bwipefs\b/,                                       reason: 'filesystem signature wipe (wipefs)',      category: 'destruction' },
  { re: /\bfdisk\b/,                                        reason: 'disk partitioning (fdisk)',               category: 'destruction' },
  { re: /\bparted\b/,                                       reason: 'disk partitioning (parted)',              category: 'destruction' },
  { re: /\bmv\s+.*\s+\/dev\/null\b/,                        reason: 'moving file to /dev/null',                category: 'destruction' },

  // --- Fork bombs / infinite spawn loops ---
  { re: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/,        reason: 'fork bomb (classic)',                     category: 'destruction' },
  { re: /\bwhile\s+true\s*;\s*do\s+.*fork\b/i,              reason: 'infinite spawn loop',                     category: 'destruction' },

  // --- Privileged / sudo escalation ---
  { re: /\bsudo\s+rm\b/,                                    reason: 'sudo rm',                                 category: 'escalation' },
  { re: /\bsudo\s+dd\b/,                                    reason: 'sudo dd',                                 category: 'escalation' },
  { re: /\bsudo\s+mkfs\b/,                                  reason: 'sudo mkfs',                               category: 'escalation' },
  { re: /\bsudo\s+chmod\s+[0-7]*777\b/,                     reason: 'sudo chmod 777',                          category: 'escalation' },
  { re: /\bsudo\s+chown\s+-[a-zA-Z]*R\b/,                   reason: 'sudo chown -R',                           category: 'escalation' },
  { re: /\bsudo\s+sh\b|\bsudo\s+bash\b|\bsudo\s+-[a-zA-Z]*i\b|\bsudo\s+su\b/, reason: 'privileged shell escalation', category: 'escalation' },

  // --- System shutdown / reboot / halt ---
  { re: /\breboot\b/,                                       reason: 'system reboot',                           category: 'shutdown' },
  { re: /\bshutdown\b/,                                     reason: 'system shutdown',                         category: 'shutdown' },
  { re: /\bhalt\b/,                                         reason: 'system halt',                             category: 'shutdown' },
  { re: /\bpoweroff\b/,                                     reason: 'system poweroff',                         category: 'shutdown' },
  { re: /\binit\s+0\b/,                                     reason: 'init 0 (halt)',                           category: 'shutdown' },

  // --- Direct writes to sensitive paths ---
  { re: />\s*\/dev\/[sh]d[a-z]/,                            reason: 'direct write to block device',            category: 'destruction' },
  { re: />\s*\/etc\//,                                      reason: 'direct overwrite of /etc/',               category: 'destruction' },
  { re: />\s*\/boot\//,                                     reason: 'direct overwrite of /boot/',              category: 'destruction' },
  { re: />\s*\/usr\//,                                      reason: 'direct overwrite of /usr/',               category: 'destruction' },
  { re: />\s*\/var\//,                                      reason: 'direct overwrite of /var/',               category: 'destruction' },

  // --- Irreversible data / history loss (seeded so allow-by-default can't run them) ---
  { re: /\bdrop\s+(table|database|schema)\b/i,             reason: 'SQL drop table/database',                 category: 'destruction' },
  { re: /\btruncate\s+table\b/i,                           reason: 'SQL truncate table',                      category: 'destruction' },
  { re: /\bgit\s+push\b[^\n]*\s(--force|-f)\b/,            reason: 'git push --force (history overwrite)',    category: 'destruction' },
  { re: /\bchmod\s+[0-7]*777\b[^\n]*(\/(?:etc|usr|var|boot|bin|sbin|lib)\b|\s\/(?:\s|$)|~)/, reason: 'chmod 777 on a system path', category: 'perms' },

  // --- Credential exfiltration / secret leakage ---
  { re: /\b(curl|wget|nc|ncat|netcat)\b.*\.(ssh|gnupg|aws|kube|config)\b/i, reason: 'credential exfil via network tool', category: 'exfiltration' },
  { re: /\b(curl|wget)\s+.*--upload-file\b.*\.(pem|key|id_rsa|id_ed25519)\b/i, reason: 'upload private key',            category: 'exfiltration' },
  { re: /\bcat\s+.*id_rsa\b.*\|\s*(curl|wget|nc)\b/,        reason: 'pipe SSH key to network',                 category: 'exfiltration' },
  { re: /\bcat\s+.*\.env\b.*\|\s*(curl|wget|nc)\b/,         reason: 'pipe .env to network',                    category: 'exfiltration' },

  // --- Remote code exec ---
  { re: /\beval\s+.*\$\(/,                                  reason: 'eval with command substitution',          category: 'exec' },
  { re: /\beval\s+.*`/,                                     reason: 'eval with backtick substitution',         category: 'exec' },
  { re: /\b(curl|wget)\s+.*\|\s*(bash|sh|zsh|source)\b/,    reason: 'curl|bash (remote code exec)',            category: 'exec' },
  { re: /\b(bash|sh|zsh)\s+<\s*\(.*\b(curl|wget)\b/,        reason: 'process substitution remote exec',        category: 'exec' },
  { re: /\bfind\b[\s\S]*\s-exec\s+(rm|rmdir|unlink|shred|dd|mkfs|sh|bash|zsh|chmod|chown|mv|truncate)\b/, reason: 'find -exec running a destructive command', category: 'exec' },

  // --- Recursive permission / ownership on sensitive paths ---
  { re: /\bchmod\s+-[a-zA-Z]*R[a-zA-Z]*\s+\S+\s+(\/(?:\s|$)|~\/|\/etc(?:\s|\/|$)|\/var(?:\s|\/|$)|\/home(?:\s|\/|$))/, reason: 'recursive chmod on sensitive path', category: 'perms' },
  { re: /\bchown\s+-[a-zA-Z]*R[a-zA-Z]*\s+\S+\s+(\/(?:\s|$)|~\/|\/etc(?:\s|\/|$)|\/var(?:\s|\/|$)|\/home(?:\s|\/|$))/, reason: 'recursive chown on sensitive path', category: 'perms' },

  // --- Mass process kill ---
  { re: /\bkillall\b/,                                      reason: 'killall',                                 category: 'service' },
  { re: /\bpkill\s+-9\b/,                                   reason: 'pkill -9',                                category: 'service' },
  { re: /\bkill\s+-9\s+-1\b/,                               reason: 'kill -9 -1 (all user procs)',             category: 'service' },

  // --- Docker / container abuse ---
  { re: /\bdocker\s+\S+\s+.*--privileged\b/,                reason: 'privileged docker container',             category: 'escalation' },
  { re: /\bdocker\s+(rm|rmi|system\s+prune)\b.*(-a|--all|-f|--force)\b/, reason: 'mass docker resource removal',       category: 'destruction' },

  // --- Interpreter one-liners with dangerous syscalls ---
  { re: /\bpython[23]?\s+-c\s+.*\b(os\.system|subprocess|shutil\.rmtree)\b/, reason: 'python -c with dangerous syscall', category: 'exec' },
  { re: /\bnode\s+-e\s+.*\b(child_process|fs\.rm|fs\.unlink)\b/,             reason: 'node -e with dangerous syscall',   category: 'exec' },

  // --- Firewall / service manipulation ---
  { re: /\biptables\s+.*-F\b|\biptables\s+--flush\b/,        reason: 'iptables flush',                          category: 'service' },
  { re: /\bufw\s+disable\b/,                                reason: 'ufw disable',                             category: 'service' },
  { re: /\bcrontab\s+-r\b/,                                 reason: 'crontab -r',                              category: 'service' },
  { re: /\bsystemctl\s+(stop|disable|mask)\b/,              reason: 'systemctl stop/disable/mask',             category: 'service' },
  { re: /\blaunchctl\s+(unload|remove)\b/,                  reason: 'launchctl unload/remove',                 category: 'service' },
];

// Snapshot pattern metadata for UI exposure (reason/category are stable,
// the compiled regex is not something we want to ship over the wire).
const PATTERN_META = PATTERNS.map((p, i) => ({
  id: i,
  source: p.re.source,
  flags: p.re.flags,
  reason: p.reason,
  category: p.category,
}));

function _normalizeCommand(input) {
  if (!input) return '';
  if (typeof input === 'string') return input;
  // approval-agent context.command is always a string, but we defend anyway.
  try { return String(input); } catch { return ''; }
}

// --- User-configurable patterns (Permission tab) -------------------------
// The built-in PATTERNS above are sensible DEFAULTS shipped with CTM. The user
// can, in the Permission tab: (a) DISABLE specific defaults (by id), and (b) ADD
// their own custom patterns. Both live in the `approval_blocklist_config` setting
// and are passed into checkBlocklist() by the approval agent. Custom regexes are
// user-authored on their own machine — still, we validate + ReDoS-guard them so a
// fat-fingered pattern can't wedge the approval path.
const CUSTOM_FLAGS_ALLOWED = 'imsu';
const CUSTOM_SOURCE_MAX = 200;
const CUSTOM_PATTERNS_MAX = 200;

// ReDoS guard — same shape as approval-agent's isSafeRegex (nested quantifiers
// are the main catastrophic-backtracking vector). Kept local so the worker has
// no cross-module dependency.
function _isSafeRegexSource(source) {
  if (/(\+|\*|\{)\)?(\+|\*|\?)/.test(source)) return false;       // nested quantifiers
  if ((source.match(/\|/g) || []).length > 20) return false;       // excessive alternation
  if (/\|\||^\||\|$/.test(source)) return false;                   // empty alternation → matches all
  return true;
}

// Validate one user pattern. Returns { ok, re?, normalized?, error? }. Pure — the
// API layer uses it to reject bad input with a message; checkBlocklist uses it to
// skip (not throw on) a stored pattern that somehow became invalid.
function validateUserPattern(p) {
  if (!p || typeof p !== 'object') return { ok: false, error: 'pattern must be an object' };
  const source = typeof p.source === 'string' ? p.source.trim() : '';
  if (!source) return { ok: false, error: 'regex is required' };
  if (source.length > CUSTOM_SOURCE_MAX) return { ok: false, error: `regex too long (max ${CUSTOM_SOURCE_MAX} chars)` };
  const flags = typeof p.flags === 'string' ? p.flags : '';
  for (const f of flags) if (!CUSTOM_FLAGS_ALLOWED.includes(f)) return { ok: false, error: `unsupported flag "${f}" (allowed: ${CUSTOM_FLAGS_ALLOWED})` };
  if (new Set(flags).size !== flags.length) return { ok: false, error: 'duplicate regex flag' };
  if (!_isSafeRegexSource(source)) return { ok: false, error: 'regex rejected (nested quantifiers / excessive alternation — possible ReDoS)' };
  let re;
  try { re = new RegExp(source, flags); } catch (e) { return { ok: false, error: `invalid regex: ${e.message}` }; }
  return {
    ok: true,
    re,
    normalized: {
      source,
      flags,
      reason: String(p.reason || '').slice(0, 200).trim() || 'Custom blocklist pattern',
      category: (String(p.category || 'custom').slice(0, 40).trim() || 'custom'),
    },
  };
}

// Coerce a raw config object into the shape checkBlocklist expects, dropping
// anything malformed. Returns { disabledIds: Set<number>, customPatterns: [{re, reason, category, id}] }.
function _resolveConfig(config) {
  const disabledIds = new Set();
  const customPatterns = [];
  if (config && typeof config === 'object') {
    const raw = Array.isArray(config.disabledIds) ? config.disabledIds : [];
    for (const id of raw) { const n = Number(id); if (Number.isInteger(n) && n >= 0 && n < PATTERNS.length) disabledIds.add(n); }
    const customs = Array.isArray(config.customPatterns) ? config.customPatterns.slice(0, CUSTOM_PATTERNS_MAX) : [];
    customs.forEach((p, i) => {
      const v = validateUserPattern(p);
      if (v.ok) customPatterns.push({ re: v.re, reason: v.normalized.reason, category: v.normalized.category, id: (p && p.id != null) ? p.id : `c${i}` });
    });
  }
  return { disabledIds, customPatterns };
}

// Check a command against the blocklist.
// Returns { blocked: true, reason, category, patternId } on first match,
// or { blocked: false } otherwise.
//
// Optional `config` ({ disabledIds, customPatterns }) layers the user's Permission
// tab edits on top of the defaults: disabled default ids are skipped, and custom
// patterns are checked after the defaults. Omitting `config` evaluates the defaults
// only (back-compat). Default matches carry a numeric `patternId`; custom matches
// carry `custom: true` + `customId` and `patternId: null`.
//
// This is synchronous and O(N patterns). The built-in patterns are anchored on
// literal keywords (\brm, \bmkfs, etc.) and custom patterns are ReDoS-guarded, so
// there's no catastrophic backtracking concern even on adversarial input.
function checkBlocklist(command, config) {
  const cmd = _normalizeCommand(command);
  if (!cmd) return { blocked: false };
  const { disabledIds, customPatterns } = _resolveConfig(config);
  // 1) Built-in defaults (skip user-disabled ids).
  for (let i = 0; i < PATTERNS.length; i++) {
    if (disabledIds.has(i)) continue;
    const p = PATTERNS[i];
    if (p.re.test(cmd)) {
      return { blocked: true, reason: p.reason, category: p.category, patternId: i };
    }
  }
  // 2) User custom patterns.
  for (const c of customPatterns) {
    if (c.re.test(cmd)) {
      return { blocked: true, reason: c.reason, category: c.category, patternId: null, custom: true, customId: c.id };
    }
  }
  return { blocked: false };
}

// Legacy helper — check multiple strings at once (e.g. tool name + command).
function checkBlocklistMulti(fields, config) {
  for (const v of fields) {
    const res = checkBlocklist(v, config);
    if (res.blocked) return res;
  }
  return { blocked: false };
}

module.exports = {
  checkBlocklist,
  checkBlocklistMulti,
  validateUserPattern,
  PATTERN_META,
  DEFAULT_PATTERN_COUNT: PATTERNS.length,
  // Exposed for tests — callers should not mutate.
  _PATTERNS: PATTERNS,
};
