'use strict';

const ALL_SCOPES = Object.freeze(['read', 'respond', 'create', 'admin']);

const WS_AUTH_RULES = Object.freeze({
  list: rule('read', false, 'never'),
  attach: rule('read', false, 'never'),
  detach: rule('read', false, 'never'),
  subscribe: rule('read', false, 'never'),
  unsubscribe: rule('read', false, 'never'),
  'subscribe-stream': rule('read', false, 'never'),
  'unsubscribe-stream': rule('read', false, 'never'),
  'subscribe-terminal-tail': rule('read', false, 'never'),
  'unsubscribe-terminal-tail': rule('read', false, 'never'),
  snapshot: rule('read', false, 'never'),
  reconcile: rule('read', false, 'never'),
  resize: rule('read', false, 'never'),
  reflow: rule('read', false, 'never'),
  'walle-history-request': rule('read', false, 'never'),

  input: rule('respond', true, 'remote'),
  'walle-message': rule('respond', true, 'remote'),
  'walle-cancel': rule('respond', true, 'remote'),
  'walle-permission-response': rule('respond', true, 'remote'),

  create: rule('create', true, 'remote'),

  kill: rule('admin', true, 'remote'),
  rename: rule('admin', true, 'remote'),
  'update-session-cwd': rule('admin', true, 'remote'),
  'model-change': rule('admin', true, 'remote'),
});

const HTTP_AUTH_RULES = Object.freeze([
  exact('GET', '/api/auth/me', 'read', false, 'never'),
  exact('GET', '/api/auth/rules', 'admin', false, 'never', { loopbackOnly: true }),
  exact('POST', '/api/auth/device-claims', 'admin', true, 'remote', { loopbackOnly: true }),
  regex('PATCH', /^\/api\/auth\/device-claims\/[^/]+$/, 'admin', true, 'remote', { loopbackOnly: true }),
  regex('DELETE', /^\/api\/auth\/device-claims\/[^/]+$/, 'admin', true, 'remote', { loopbackOnly: true }),
  exact('GET', '/api/auth/pairing-requests', 'admin', false, 'never', { loopbackOnly: true }),
  regex('POST', /^\/api\/auth\/pairing-requests\/[^/]+\/(?:approve|reject)$/, 'admin', true, 'remote', { loopbackOnly: true }),
  exact('GET', '/api/auth/devices', 'admin', false, 'never', { loopbackOnly: true }),
  exact('POST', '/api/auth/device-duplicates/revoke', 'admin', true, 'remote', { loopbackOnly: true }),
  regex('PATCH', /^\/api\/auth\/devices\/[^/]+$/, 'admin', true, 'remote', { loopbackOnly: true }),
  regex('DELETE', /^\/api\/auth\/devices\/[^/]+$/, 'admin', true, 'remote', { loopbackOnly: true }),
  exact('POST', '/api/auth/revoke-all', 'admin', true, 'remote', { loopbackOnly: true }),
  exact('GET', '/api/auth/audit', 'admin', false, 'never', { loopbackOnly: true }),
  exact('POST', '/api/auth/ws-ticket', 'read', false, 'never'),
  exact('POST', '/api/auth/begin-step-up', 'read', true, 'never'),
  exact('POST', '/api/auth/finish-step-up', 'read', true, 'never'),
  exact('POST', '/api/auth/register-passkey', 'respond', true, 'remote'),
  exact('POST', '/api/auth/revoke-self', 'read', true, 'remote'),

  exact('GET', '/api/push/vapid-public-key', 'read', false, 'never'),
  exact('POST', '/api/push/subscribe', 'respond', true, 'remote'),
  exact('DELETE', '/api/push/subscribe', 'respond', true, 'remote'),
  regex('DELETE', /^\/api\/push\/subscribe\/[^/]+$/, 'respond', true, 'remote'),
  exact('POST', '/api/push/test', 'respond', true, 'remote'),
  exact('GET', '/api/push/settings', 'read', false, 'never'),
  exact('PUT', '/api/push/settings', 'respond', true, 'remote'),

  exact('GET', '/api/remote/status', 'read', false, 'never'),
  exact('GET', '/api/remote/registry', 'read', false, 'never'),
  exact('GET', '/api/remote/submissions', 'read', false, 'never'),
  exact('GET', '/api/remote/audit', 'admin', false, 'never', { loopbackOnly: true }),
  exact('POST', '/api/remote/pairing-claims', 'admin', true, 'remote', { loopbackOnly: true }),
  exact('POST', '/api/remote/messages', 'respond', true, 'never'),
  regex('POST', /^\/api\/remote\/devices\/[^/]+\/revoke$/, 'admin', true, 'remote', { loopbackOnly: true }),
  exact('POST', '/api/mobile/attachments/upload', 'respond', true, 'never'),

  exact('POST', '/api/permissions/walle-check', 'admin', true, 'remote', { loopbackOnly: true }),
  exact('POST', '/api/permissions/walle-record', 'admin', true, 'remote', { loopbackOnly: true }),

  exact('GET', '/api/setup/network', 'admin', false, 'never'),
  exact('GET', '/api/setup/network/microsoft-dev-tunnel/probe', 'admin', false, 'never'),
  regex('GET', /^\/api\/setup\/(?:status|test-key|detect-[^/]+|providers|all-models|task-default|embeddings|oauth-proxy\/status)$/, 'read', false, 'never'),
  exact('GET', '/api/setup/list-models', 'read', false, 'never'),
  exact('POST', '/api/setup/list-models', 'read', false, 'never'),
  regex('POST', /^\/api\/setup\//, 'admin', true, 'remote'),

  exact('GET', '/api/models/available-agents', 'read', false, 'never'),
  regex('GET', /^\/api\/models(?:\/|$)/, 'read', false, 'never'),

  exact('GET', '/api/projects', 'read', false, 'never'),
  exact('GET', '/api/rules', 'read', false, 'never'),
  exact('GET', '/api/rules/list', 'read', false, 'never'),
  exact('PUT', '/api/rules', 'admin', true, 'remote'),

  exact('GET', '/api/recent-sessions', 'read', false, 'never'),
  exact('GET', '/api/sessions/standup', 'read', false, 'never'),
  exact('GET', '/api/sessions/git-status', 'read', false, 'never'),
  exact('GET', '/api/session/prompts', 'read', false, 'never'),
  exact('GET', '/api/session/messages', 'read', false, 'never'),
  exact('GET', '/api/session/runtime-diagnostics', 'read', false, 'never'),
  exact('GET', '/api/session/export', 'read', false, 'never'),
  exact('GET', '/api/session/prompts', 'read', false, 'never'),
  exact('POST', '/api/session/image-refs', 'respond', true, 'never'),
  exact('GET', '/api/sessions/integrity', 'read', false, 'never'),
  exact('GET', '/api/sessions/relink-audit', 'read', false, 'never'),
  exact('GET', '/api/sessions/search', 'read', false, 'never'),
  exact('GET', '/api/sessions/search-status', 'read', false, 'never'),
  exact('GET', '/api/sessions/analysis', 'read', false, 'never'),
  exact('GET', '/api/stream/status', 'read', false, 'never'),
  regex('GET', /^\/api\/sessions\/[^/]+\/(?:diagnostics|stream|summary)$/, 'read', false, 'never'),
  regex('POST', /^\/api\/sessions\/[^/]+\/attention-dismiss$/, 'respond', true, 'never'),
  exact('POST', '/api/sessions/ai-search', 'read', false, 'never'),
  exact('POST', '/api/sessions/analyze', 'read', false, 'never'),
  exact('POST', '/api/sessions/resume', 'create', true, 'remote'),
  exact('POST', '/api/sessions/attach', 'create', true, 'remote'),
  exact('POST', '/api/sessions/rename', 'respond', true, 'never'),
  regex('POST', /^\/api\/sessions\//, 'admin', true, 'remote'),
  regex('POST', /^\/api\/session\//, 'admin', true, 'remote'),
  exact('DELETE', '/api/session', 'admin', true, 'remote'),

  regex('GET', /^\/api\/ctm\/session-memory\/(?:health|search|context)$/, 'read', false, 'never'),
  exact('POST', '/api/ctm/session-memory/context-pack', 'read', false, 'never'),
  regex('POST', /^\/api\/ctm\/session-memory\/(?:message-index\/backfill|rebuild-message-index)$/, 'admin', true, 'remote'),

  exact('GET', '/api/health', 'read', false, 'never'),
  exact('GET', '/api/services/status', 'read', false, 'never'),
  regex('GET', /^\/api\/approver\/(?:status|debug\/)/, 'read', false, 'never'),
  exact('GET', '/api/app/version', 'read', false, 'never'),
  exact('GET', '/api/updates/check', 'read', false, 'never'),
  regex('POST', /^\/api\/updates\//, 'admin', true, 'remote'),
  exact('POST', '/api/restart/ctm', 'admin', true, 'never'),
  regex('POST', /^\/api\/(?:restart|start|stop)\//, 'admin', true, 'remote'),

  // Wall-E self-healing repair. CTM-local (no-hyphen path) so they are NOT caught
  // by the /api/wall-e/* daemon proxy — the supervisor lives in CTM and the daemon
  // may be down. start/dismiss spawn or cancel a full-auto repair agent, so they
  // require the same admin + step-up posture as service control.
  exact('GET', '/api/walle/repair/status', 'read', false, 'never'),
  exact('POST', '/api/walle/repair/start', 'admin', true, 'remote'),
  exact('POST', '/api/walle/repair/dismiss', 'admin', true, 'remote'),

  exact('GET', '/api/hooks', 'read', false, 'never'),
  regex('POST', /^\/api\/hooks(?:\/|$)/, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/hooks\//, 'admin', true, 'remote'),
  regex('GET', /^\/api\/otel\/(?:status|plan-install)$/, 'read', false, 'never'),
  regex('POST', /^\/api\/otel\//, 'admin', true, 'remote'),

  exact('GET', '/api/workspaces', 'read', false, 'never'),
  regex('POST', /^\/api\/workspaces(?:\/|$)/, 'admin', true, 'remote'),
  regex('PUT', /^\/api\/workspaces\//, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/workspaces\//, 'admin', true, 'remote'),

  exact('GET', '/api/worktrees', 'read', false, 'never'),
  exact('POST', '/api/worktrees/create', 'create', true, 'remote'),
  regex('GET', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*$/, 'read', false, 'never'),
  regex('POST', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*\/cancel$/, 'admin', true, 'remote'),
  regex('POST', /^\/api\/worktrees\//, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/worktrees\//, 'admin', true, 'remote'),

  regex('GET', /^\/api\/(?:prompts?|folders|images|chains|permissions|conversations|templates|session-prompts|settings|hotkey|backups|tool-permissions|auto-approvals|approval-rules|approval-decisions|approval-escalations|approval\/blocklist|hooks\/status|queues|queue-linked-prompts|queue-draft|harvest|autocomplete|palette|ghost-complete|similar|patterns|copilot|prompt-quality|prompt-executions|frequent-questions|session-trajectory|skills\/(?:autocomplete|resolve-intent))(?:\/|$)/, 'read', false, 'never'),
  exact('POST', '/api/prompts/ai-search', 'read', false, 'never'),
  exact('POST', '/api/patterns/detect', 'read', false, 'never'),
  exact('POST', '/api/prompt-context', 'read', false, 'never'),
  exact('POST', '/api/queues', 'respond', true, 'never'),
  regex('POST', /^\/api\/queues\/[^/]+\/next$/, 'respond', true, 'never'),
  regex('PUT', /^\/api\/queue-draft\/[^/]+$/, 'respond', true, 'never'),
  regex('POST', /^\/api\/copilot\/(?:suggest|chat)$/, 'respond', true, 'remote'),
  regex('POST', /^\/api\/approval-decisions\/\d+\/resolve$/, 'respond', true, 'remote'),
  regex('POST', /^\/api\/prompt-executions\/\d+\/outcome$/, 'respond', true, 'remote'),
  regex('POST', /^\/api\/(?:prompts?|folders|images|chains|permissions|conversations|templates|settings|screenshot|hotkey|backups|tool-permissions|auto-approvals|approval-rules|approval-escalations|approval\/blocklist|hooks\/status|queues|queue-linked-prompts|queue-draft|harvest|patterns|lifecycle|effectiveness)(?:\/|$)/, 'admin', true, 'remote'),
  regex('PUT', /^\/api\/(?:prompts?|folders|images|chains|settings|hooks\/status)(?:\/|$)/, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/(?:prompts?|folders|images|chains|backups|tool-permissions|auto-approvals|approval-rules|queues|queue-draft)(?:\/|$)/, 'admin', true, 'remote'),

  regex('GET', /^\/api\/prompts(?:\/|$)/, 'read', false, 'never'),
  regex('POST', /^\/api\/prompts(?:\/|$)/, 'admin', true, 'remote'),
  regex('PUT', /^\/api\/prompts(?:\/|$)/, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/prompts(?:\/|$)/, 'admin', true, 'remote'),

  regex('PUT', /^\/api\/review-comments\//, 'respond', true, 'remote'),
  regex('DELETE', /^\/api\/review-comments\//, 'respond', true, 'remote'),
  regex('GET', /^\/api\/reviews?(?:\/|$)/, 'read', false, 'never'),
  regex('POST', /^\/api\/reviews?(?:\/|$)/, 'respond', true, 'remote'),
  regex('PUT', /^\/api\/reviews?(?:\/|$)/, 'respond', true, 'remote'),
  regex('DELETE', /^\/api\/reviews?(?:\/|$)/, 'admin', true, 'remote'),

  regex('GET', /^\/api\/feedback(?:\/|$)/, 'read', false, 'never'),
  regex('POST', /^\/api\/feedback(?:\/|$)/, 'respond', true, 'remote'),
  regex('PUT', /^\/api\/feedback(?:\/|$)/, 'respond', true, 'remote'),
  regex('DELETE', /^\/api\/feedback(?:\/|$)/, 'admin', true, 'remote'),

  exact('GET', '/api/wall-e/memories/search', 'read', false, 'never'),
  regex('GET', /^\/api\/wall-e\//, 'read', false, 'never'),
  exact('POST', '/api/wall-e/chat', 'respond', true, 'remote'),
  regex('POST', /^\/api\/wall-e\/tasks\/[^/]+\/stop$/, 'respond', true, 'remote'),
  exact('POST', '/api/wall-e/tasks', 'create', true, 'remote'),
  regex('POST', /^\/api\/wall-e\//, 'admin', true, 'remote'),
  regex('PUT', /^\/api\/wall-e\//, 'admin', true, 'remote'),
  regex('DELETE', /^\/api\/wall-e\//, 'admin', true, 'remote'),
]);

function rule(scope, mutation, stepUp, extra = {}) {
  return Object.freeze({ scope, mutation: !!mutation, stepUp, ...extra });
}

function exact(method, path, scope, mutation, stepUp, extra = {}) {
  return Object.freeze({ kind: 'http', method, path, exact: true, ...rule(scope, mutation, stepUp, extra) });
}

function regex(method, pattern, scope, mutation, stepUp, extra = {}) {
  return Object.freeze({ kind: 'http', method, path: pattern, exact: false, ...rule(scope, mutation, stepUp, extra) });
}

function getHttpAuthRule(method, pathname) {
  const m = String(method || '').toUpperCase();
  for (const r of HTTP_AUTH_RULES) {
    if (r.method !== m) continue;
    if (r.exact && r.path === pathname) return r;
    if (!r.exact && r.path.test(pathname)) return r;
  }
  return null;
}

function getWsAuthRule(messageType) {
  return WS_AUTH_RULES[String(messageType || '')] || null;
}

function hasScope(auth, requiredScope) {
  if (!requiredScope) return true;
  const scopes = auth?.scopes instanceof Set ? auth.scopes : new Set(auth?.scopes || []);
  return scopes.has('admin') || scopes.has(requiredScope);
}

function authorize(auth, rule, options = {}) {
  if (!rule) return { ok: false, status: 403, code: 'auth_rule_missing' };
  if (!auth?.authenticated) return { ok: false, status: 401, code: 'unauthorized' };
  if (rule.loopbackOnly && !auth.isLoopback) return { ok: false, status: 403, code: 'loopback_only' };
  if (!hasScope(auth, rule.scope)) return { ok: false, status: 403, code: 'insufficient_scope' };
  if (options.enforceStepUp && !auth.isLoopback && rule.stepUp === 'remote' && !auth.stepUpFresh) {
    return { ok: false, status: 403, code: 'step_up_required' };
  }
  return { ok: true };
}

function publicRule(rule) {
  if (!rule) return null;
  return {
    method: rule.method,
    path: rule.path instanceof RegExp ? rule.path.source : rule.path,
    exact: !!rule.exact,
    scope: rule.scope,
    mutation: rule.mutation,
    stepUp: rule.stepUp,
    loopbackOnly: !!rule.loopbackOnly,
  };
}

function listHttpAuthRules() {
  return HTTP_AUTH_RULES.map(publicRule);
}

function listWsAuthRules() {
  return Object.keys(WS_AUTH_RULES).sort().map((type) => ({ type, ...publicRule(WS_AUTH_RULES[type]) }));
}

module.exports = {
  ALL_SCOPES,
  HTTP_AUTH_RULES,
  WS_AUTH_RULES,
  authorize,
  getHttpAuthRule,
  getWsAuthRule,
  hasScope,
  listHttpAuthRules,
  listWsAuthRules,
};
