'use strict';

// Helpers for the Permission "Needs Review" surface: mask secrets so the review UI
// never re-spills real credentials, group escalation rows by command type so a huge
// pile collapses to a handful of reviewable types, and suggest a narrow allow rule.

function _maskValue(v, keep = 4) {
  const s = String(v || '');
  if (s.length <= keep + 2) return '••••';
  return s.slice(0, keep) + '••••' + s.slice(-2);
}

// Mask AWS keys, KEY/SECRET/TOKEN/PASSWORD assignments, bearer + common provider
// tokens in any command/context text shown to the client. Best-effort, not crypto.
function maskSecrets(text) {
  let out = String(text || '');
  out = out.replace(/\bAKIA[0-9A-Z]{12,}\b/g, (m) => m.slice(0, 4) + '••••' + m.slice(-2));
  out = out.replace(
    /\b([A-Za-z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CREDENTIAL)[A-Za-z0-9_]*)(\s*=\s*)(["']?)([^\s"'&;|]{4,})\3/gi,
    (m, name, eq, q, val) => `${name}${eq}${q}${_maskValue(val)}${q}`,
  );
  out = out.replace(/\b(Bearer\s+)([A-Za-z0-9._-]{12,})/gi, (m, p, t) => p + _maskValue(t));
  out = out.replace(
    /\b((?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{16,}|sk-[A-Za-z0-9]{16,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g,
    (m) => _maskValue(m, 6),
  );
  return out;
}

// Strip leading shell environment-variable assignments (FOO=bar, including
// secret VALUES) from a command. "AWS_ACCESS_KEY_ID=AKIA… AWS_SECRET_ACCESS_KEY=…
// aws ecr get-login" → { rest: "aws ecr get-login", envNames: ["AWS_ACCESS_KEY_ID",
// "AWS_SECRET_ACCESS_KEY"] }. Without this, the (varying) secret value lands in the
// grouping key and every invocation becomes its own one-off "type".
function stripEnvAssignments(text) {
  const tokens = String(text || '').trim().split(/\s+/).filter(Boolean);
  let i = 0;
  const envNames = [];
  while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) {
    envNames.push(tokens[i].slice(0, tokens[i].indexOf('=')));
    i += 1;
  }
  return { rest: tokens.slice(i).join(' '), envNames };
}

// The "command head" is the command + its subcommands, up to the first flag or
// normalized-arg placeholder — e.g. "aws eks update-kubeconfig --name octo" and
// "… --name prod" both reduce to "aws eks update-kubeconfig". This is the grouping
// TYPE: arg variations collapse, but distinct operations stay distinct. Leading
// env-credential prefixes are stripped first so they don't fragment the grouping.
// Skip a leading run of `cd <path>` / `echo <arg>` framing token-pairs so the head
// is the operative command, not navigation/logging. Defense-in-depth: new signatures
// are already framing-stripped upstream (approval-agent `_stripFramingPrefix`), but
// OLD escalation rows persisted before that fix still lead with `cd`. Keeps the
// original tokens if framing was the ENTIRE command (a bare `cd x` stays `cd`).
function _stripLeadingFramingTokens(tokens) {
  let i = 0;
  while (i + 1 < tokens.length && (tokens[i] === 'cd' || tokens[i] === 'echo')) i += 2;
  const rest = tokens.slice(i);
  return rest.length ? rest : tokens;
}
function commandHead(text) {
  const s = String(text || '').trim();
  if (!s || s === 'unknown') return '';
  const { rest, envNames } = stripEnvAssignments(s);
  const head = [];
  for (const t of _stripLeadingFramingTokens(rest.split(/\s+/).filter(Boolean))) {
    if (!t) continue;
    if (t.startsWith('-')) break;                 // flag
    if (/^[<{[(].*[>}\])]$/.test(t)) break;       // <arg>/<path>/<num> placeholder
    if (/[/@:=]/.test(t) && head.length >= 1) break; // a path/url/image arg after the verb
    head.push(t);
    if (head.length >= 4) break;
  }
  if (head.length) return head.join(' ').trim();
  // The command was nothing but env assignments — group by the sorted var NAMES so
  // distinct secret VALUES still collapse into one reviewable type.
  if (envNames.length) return 'env ' + envNames.slice().sort().join(' ');
  return '';
}

// Display title for a group: prefer the command with any leading env-credential
// prefix stripped so the row reads "aws ecr get-login", not the masked secret.
function cleanTitle(text) {
  const raw = String(text || '').trim();
  if (!raw || raw === 'Bash command') return '';
  const { rest } = stripEnvAssignments(raw);
  return rest || raw;
}

// Group escalation rows (db approval_decisions where decision='escalated') by
// command type. keyFn(row) -> a stable grouping signature; defaults to the row's
// command_signature, falling back to summary/tool. Returns groups sorted lastSeen↓.
function groupEscalations(rows, keyFn) {
  const groups = new Map();
  for (const row of (Array.isArray(rows) ? rows : [])) {
    const base = String(row.command_signature || (keyFn ? keyFn(row) : '') || row.command_summary || row.tool_name || '').trim();
    const key = commandHead(base) || base || 'unknown';
    let g = groups.get(key);
    if (!g) {
      g = {
        key,
        title: cleanTitle(row.command_summary) || (key && key !== 'unknown' && !key.startsWith('env ') ? key : (row.tool_name || 'command')),
        toolName: row.tool_name || '',
        riskLevel: row.risk_level || 'medium',
        count: 0,
        ids: [],
        sessions: new Set(),
        firstSeen: row.created_at || '',
        lastSeen: row.created_at || '',
        reasoning: row.reasoning || '',
        sampleContext: row.full_context || '',
      };
      groups.set(key, g);
    }
    g.count += 1;
    g.ids.push(row.id);
    if (row.session_id) g.sessions.add(row.session_id);
    if (row.created_at && (!g.lastSeen || row.created_at > g.lastSeen)) {
      g.lastSeen = row.created_at;
      g.reasoning = row.reasoning || g.reasoning;
      g.sampleContext = row.full_context || g.sampleContext;
    }
    if (row.created_at && (!g.firstSeen || row.created_at < g.firstSeen)) g.firstSeen = row.created_at;
    if (row.risk_level === 'high') g.riskLevel = 'high';
  }
  return Array.from(groups.values())
    .map((g) => ({
      key: g.key,
      title: maskSecrets(g.title),
      toolName: g.toolName,
      riskLevel: g.riskLevel,
      count: g.count,
      ids: g.ids,
      sessionCount: g.sessions.size,
      firstSeen: g.firstSeen,
      lastSeen: g.lastSeen,
      reasoning: g.reasoning,
      sampleCommand: maskSecrets(String(g.sampleContext || '').slice(0, 1200)),
      suggestedRule: suggestAllowRule(g),
    }))
    .sort((a, b) => String(b.lastSeen || '').localeCompare(String(a.lastSeen || '')));
}

// Suggest a NARROW Claude-syntax allow pattern from a group's signature/title.
// "aws eks update-kubeconfig <arg>" -> "Bash(aws eks update-kubeconfig:*)".
function suggestAllowRule(group) {
  const prefix = commandHead(String((group && (group.key || group.title)) || '').trim());
  return prefix ? `Bash(${prefix}:*)` : '';
}

module.exports = { maskSecrets, groupEscalations, suggestAllowRule, commandHead };
