'use strict';

const { validateSafeUrl } = require('./ssrf');

const TRUSTED_PLUGIN_SOURCES = new Set(['bundled', 'core', 'trusted']);
const RISKY_PLUGIN_PERMISSIONS = new Set(['network', 'shell', 'browser', 'childAgent', 'childAgents', 'elevatedExec', 'filesystemWrite']);

function auditSecurityConfig(config = {}, options = {}) {
  const findings = [];

  findings.push(...auditRedaction(config.redaction || config.logging || {}));
  findings.push(...auditPermissionRules(config.permissionRules || config.permissions?.rules || config.permissions || []));
  findings.push(...auditApprovalProfiles(config.approvalProfiles || {}, options));
  findings.push(...auditPlugins(config.plugins || config.pluginCandidates || []));
  findings.push(...auditChannels(config.channels || config.channelPolicies || []));
  findings.push(...auditNetworkEndpoints(config.urls || config.networkEndpoints || []));

  const summary = summarizeFindings(findings);
  return {
    ok: summary.critical === 0 && summary.high === 0,
    summary,
    findings,
  };
}

function auditPermissionRules(rules = []) {
  const findings = [];
  for (const rule of normalizeRules(rules)) {
    if (!rule || rule.action !== 'allow') continue;
    const permission = String(rule.permission || rule.type || '').toLowerCase();
    const pattern = String(rule.pattern || rule.value || '*');
    if (['bash', 'shell', 'run_shell'].includes(permission) && pattern === '*') {
      findings.push(finding('high', 'wildcard-shell-allow', 'Shell permission allows every command', { rule }));
    }
    if (['bash', 'shell', 'run_shell'].includes(permission) && isRiskyInterpreterPattern(pattern)) {
      findings.push(finding('medium', 'risky-interpreter-allow', 'Shell permission allows an interpreter escape pattern', { rule }));
    }
    if (permission === '*' && pattern === '*') {
      findings.push(finding('high', 'wildcard-permission-allow', 'Permission profile allows every permission and pattern', { rule }));
    }
  }
  return findings;
}

function validateApprovalProfile(childProfile = {}, parentProfile = {}) {
  const parent = normalizeApprovalProfile(parentProfile);
  const child = normalizeApprovalProfile(childProfile);
  const findings = [];

  for (const capability of ['shell', 'network', 'browser', 'childAgents', 'filesystemWrite', 'elevatedExec']) {
    if (child[capability].enabled && !parent[capability].enabled) {
      findings.push(finding('high', 'approval-profile-escalation', `Child approval profile enables ${capability} beyond parent profile`, { capability }));
    }
  }

  if (child.shell.wildcard && !parent.shell.wildcard) {
    findings.push(finding('high', 'approval-profile-shell-wildcard', 'Child approval profile allows wildcard shell commands beyond parent profile'));
  }

  if (child.network.allowLocal && !parent.network.allowLocal) {
    findings.push(finding('high', 'approval-profile-local-network', 'Child approval profile allows local/private network access beyond parent profile'));
  }

  return {
    ok: findings.length === 0,
    findings,
    parent,
    child,
  };
}

function auditApprovalProfiles(profiles = {}, options = {}) {
  const parent = options.parentApprovalProfile || profiles.parent || profiles.default || {};
  const children = Array.isArray(profiles)
    ? profiles
    : Object.entries(profiles.children || profiles.childProfiles || profiles)
      .filter(([name]) => !['parent', 'default', 'children', 'childProfiles'].includes(name))
      .map(([name, profile]) => ({ name, ...profile }));

  const findings = [];
  for (const profile of children) {
    const result = validateApprovalProfile(profile, parent);
    for (const item of result.findings) {
      findings.push({ ...item, details: { ...item.details, profile: profile.name || profile.id || 'child' } });
    }
  }
  return findings;
}

function auditPluginCandidate(candidate = {}) {
  const findings = [];
  const source = candidate.source || candidate.sourceTrust || 'unknown';
  const manifest = candidate.manifest || candidate;
  const permissions = { ...(manifest.permissions || {}), ...(candidate.permissions || {}) };
  const pluginId = manifest.id || candidate.id || candidate.name || candidate.dir || 'unknown';

  for (const diagnostic of candidate.diagnostics || []) {
    if (diagnostic.level === 'error' && /world-writable|escapes root|too large/i.test(diagnostic.message || '')) {
      findings.push(finding('high', 'unsafe-plugin-package', 'Plugin package failed discovery security checks', {
        pluginId,
        source,
        diagnostic,
      }));
    }
  }

  if (!TRUSTED_PLUGIN_SOURCES.has(source)) {
    for (const [key, value] of Object.entries(permissions)) {
      if (value === true && RISKY_PLUGIN_PERMISSIONS.has(key)) {
        findings.push(finding(key === 'network' ? 'medium' : 'high', 'untrusted-plugin-permission', `Untrusted plugin requests ${key} permission`, {
          pluginId,
          source,
          permission: key,
        }));
      }
    }
  }

  return findings;
}

function auditPlugins(plugins = []) {
  return (plugins || []).flatMap(plugin => auditPluginCandidate(plugin));
}

function auditChannels(channels = []) {
  const findings = [];
  for (const channel of normalizeChannelEntries(channels)) {
    const policy = channel.policy || channel;
    const id = channel.id || channel.name || policy.id || 'unknown';
    if (policy.group?.defaultOpen === true || policy.defaultOpen === true) {
      findings.push(finding('medium', 'channel-group-open', 'Group channel policy is open by default', { channel: id }));
    }
    const dm = policy.dm || policy.directMessages || {};
    const dmHasAllowlist = Array.isArray(dm.allowUsers || dm.allowlist) && (dm.allowUsers || dm.allowlist).length > 0;
    if (dm.enabled !== false && dm.pairingRequired !== true && !dmHasAllowlist) {
      findings.push(finding('low', 'channel-dm-open', 'Direct-message channel policy has no allowlist or pairing requirement', { channel: id }));
    }
  }
  return findings;
}

function auditNetworkEndpoints(urls = []) {
  const findings = [];
  for (const entry of urls || []) {
    const url = typeof entry === 'string' ? entry : entry.url;
    if (!url) continue;
    const result = validateSafeUrl(url);
    if (!result.ok) {
      findings.push(finding('high', 'unsafe-network-endpoint', 'Configured network endpoint fails SSRF safety checks', {
        url,
        reason: result.reason,
      }));
    }
  }
  return findings;
}

function auditRedaction(redaction = {}) {
  if (redaction.enabled === false || redaction.redactSensitive === false || redaction.sensitive === false) {
    return [finding('high', 'redaction-disabled', 'Sensitive-value redaction is disabled')];
  }
  return [];
}

function normalizeApprovalProfile(profile = {}) {
  return {
    shell: normalizeShellCapability(profile),
    network: normalizeNetworkCapability(profile),
    browser: normalizeBooleanCapability(profile, ['browser']),
    childAgents: normalizeBooleanCapability(profile, ['childAgents', 'childAgent']),
    filesystemWrite: normalizeBooleanCapability(profile, ['filesystemWrite', 'write', 'edit']),
    elevatedExec: normalizeBooleanCapability(profile, ['elevatedExec', 'elevated', 'sudo']),
  };
}

function normalizeShellCapability(profile = {}) {
  const raw = profile.shell ?? profile.bash ?? profile.permissions?.shell ?? profile.permissions?.bash ?? profile.tools?.run_shell;
  const allow = [
    ...listValues(raw && typeof raw === 'object' ? raw.allow || raw.allowlist || raw.patterns : []),
    ...listValues(profile.allowedShell || profile.shellAllow || profile.bashAllow),
  ];
  if (typeof raw === 'string' && !['allow', 'ask', 'deny'].includes(raw)) allow.push(raw);
  const enabled = raw === true || raw === 'allow' || allow.length > 0;
  return {
    enabled,
    allow,
    wildcard: raw === true || raw === 'allow' || allow.includes('*'),
  };
}

function normalizeNetworkCapability(profile = {}) {
  const raw = profile.network ?? profile.permissions?.network;
  const enabled = raw === true || raw === 'allow' || Boolean(raw && typeof raw === 'object' && raw.enabled !== false);
  const allowLocal = Boolean(raw && typeof raw === 'object' && (raw.allowLocal || raw.allowPrivate || raw.localhost));
  return {
    enabled,
    allowLocal,
  };
}

function normalizeBooleanCapability(profile = {}, keys = []) {
  for (const key of keys) {
    const direct = profile[key];
    const permission = profile.permissions && profile.permissions[key];
    const tool = profile.tools && profile.tools[key];
    const value = direct ?? permission ?? tool;
    if (value === true || value === 'allow') return { enabled: true };
  }
  return { enabled: false };
}

function normalizeRules(rules) {
  if (Array.isArray(rules)) return rules;
  if (!rules || typeof rules !== 'object') return [];
  const out = [];
  for (const [permission, value] of Object.entries(rules)) {
    if (typeof value === 'string') {
      out.push({ permission, pattern: '*', action: value });
    } else if (value && typeof value === 'object') {
      for (const [pattern, action] of Object.entries(value)) {
        out.push({ permission, pattern, action });
      }
    }
  }
  return out;
}

function normalizeChannelEntries(channels) {
  if (Array.isArray(channels)) return channels;
  if (!channels || typeof channels !== 'object') return [];
  return Object.entries(channels).map(([id, policy]) => ({ id, policy }));
}

function listValues(value) {
  if (!value) return [];
  return (Array.isArray(value) ? value : [value]).map(item => String(item)).filter(Boolean);
}

function isRiskyInterpreterPattern(pattern) {
  return /^(bash|sh|zsh)\s+-c\b/.test(pattern) ||
    /^(node|python|python3|ruby|perl)\s+(-e|-c)\b/.test(pattern);
}

function summarizeFindings(findings = []) {
  return findings.reduce((summary, item) => {
    summary[item.severity] = (summary[item.severity] || 0) + 1;
    summary.total += 1;
    return summary;
  }, { critical: 0, high: 0, medium: 0, low: 0, total: 0 });
}

function finding(severity, code, message, details = {}) {
  return { severity, code, message, details };
}

module.exports = {
  auditApprovalProfiles,
  auditChannels,
  auditNetworkEndpoints,
  auditPermissionRules,
  auditPluginCandidate,
  auditSecurityConfig,
  normalizeApprovalProfile,
  validateApprovalProfile,
};
