/**
 * Per-tool destination allowlists.
 *
 * Each tool that writes to an external service can require its destination
 * to be on an explicit allowlist before the call goes out. This prevents an
 * LLM from emailing the wrong domain, merging the wrong repo, or deleting
 * the wrong DNS zone.
 *
 * Usage:
 *   - Tool definitions in registry.js set `allowlist_required: true` and
 *     `allowlist_scope_key` (e.g. 'recipient_domain', 'repo_full_name').
 *   - Before calling an adapter, executeRegistryTool() calls assertAllowed()
 *     with the extracted scope value.
 *   - Owners manage entries via /api/tool-allowlists.
 *
 * Default-deny vs default-allow:
 *   - Tools with `allowlist_default_allow: false` (the default): an EMPTY
 *     allowlist blocks ALL calls. The owner must add at least one entry.
 *   - Tools with `allowlist_default_allow: true`: an empty allowlist permits
 *     everything (useful for opt-in scoping like web_search).
 *
 * Note: this is a DESTINATION allowlist for outbound tool calls.
 * The WhatsApp router has its own SENDER allowlist (`whatsapp_allowlist`
 * table, `lib/whatsapp-router.js`) — different semantics, kept separate.
 */

import { getDb, nowIso, newId } from './db.js';

/**
 * Check whether a destination is allowed for a given tool.
 *
 * @param {string} toolName
 * @param {{ scope_key: string, scope_value: string }} scope
 * @param {{ default_allow?: boolean }} [options]
 * @returns {{ ok: boolean, missing_entry?: { tool_name, scope_key, scope_value } }}
 */
export function assertAllowed(toolName, { scope_key, scope_value }, options = {}) {
  const db = getDb();
  const defaultAllow = Boolean(options.default_allow);

  const entries = db.prepare(
    'SELECT id FROM tool_allowlists WHERE tool_name = ? AND scope_key = ?'
  ).all(toolName, scope_key);

  // Empty allowlist: respect default_allow flag.
  if (entries.length === 0) {
    if (defaultAllow) return { ok: true };
    return { ok: false, missing_entry: { tool_name: toolName, scope_key, scope_value } };
  }

  // Check for an exact match on scope_value.
  const match = db.prepare(
    'SELECT id FROM tool_allowlists WHERE tool_name = ? AND scope_key = ? AND scope_value = ?'
  ).get(toolName, scope_key, String(scope_value));

  if (match) return { ok: true };
  return { ok: false, missing_entry: { tool_name: toolName, scope_key, scope_value } };
}

/**
 * Add a new allowlist entry.
 * Writes an audit_logs row with type 'tool_allowlist.add'.
 *
 * @param {{ tool_name: string, scope_key: string, scope_value: string, label?: string }} entry
 * @returns {{ id: string, tool_name: string, scope_key: string, scope_value: string, label: string|null, created_at: string }}
 */
export function addEntry({ tool_name, scope_key, scope_value, label }) {
  if (!tool_name) throw new Error('tool_name is required');
  if (!scope_key) throw new Error('scope_key is required');
  if (!scope_value && scope_value !== 0) throw new Error('scope_value is required');

  const db = getDb();
  const id = newId('tal');
  const now = nowIso();
  const labelStr = label ? String(label).trim() : null;

  db.prepare(`
    INSERT INTO tool_allowlists (id, tool_name, scope_key, scope_value, label, created_at)
    VALUES (?, ?, ?, ?, ?, ?)
  `).run(id, String(tool_name), String(scope_key), String(scope_value), labelStr, now);

  db.prepare(`
    INSERT INTO audit_logs (id, at, type, summary, status, meta)
    VALUES (?, ?, ?, ?, ?, ?)
  `).run(
    newId('audit'),
    now,
    'tool_allowlist.add',
    `Allowlist entry added: ${tool_name} / ${scope_key} = ${scope_value}`,
    'ok',
    JSON.stringify({ tool_name, scope_key, scope_value, label: labelStr, entry_id: id })
  );

  return { id, tool_name, scope_key, scope_value, label: labelStr, created_at: now };
}

/**
 * Remove an allowlist entry by id.
 * Writes an audit_logs row with type 'tool_allowlist.remove'.
 *
 * @param {string} id
 */
export function removeEntry(id) {
  if (!id) throw new Error('id is required');

  const db = getDb();
  const row = db.prepare('SELECT tool_name, scope_key, scope_value FROM tool_allowlists WHERE id = ?').get(id);
  if (!row) throw new Error(`Allowlist entry not found: ${id}`);

  db.prepare('DELETE FROM tool_allowlists WHERE id = ?').run(id);

  db.prepare(`
    INSERT INTO audit_logs (id, at, type, summary, status, meta)
    VALUES (?, ?, ?, ?, ?, ?)
  `).run(
    newId('audit'),
    nowIso(),
    'tool_allowlist.remove',
    `Allowlist entry removed: ${row.tool_name} / ${row.scope_key} = ${row.scope_value}`,
    'ok',
    JSON.stringify({ tool_name: row.tool_name, scope_key: row.scope_key, scope_value: row.scope_value, entry_id: id })
  );
}

/**
 * List all allowlist entries for a specific tool.
 *
 * @param {string} toolName
 * @returns {Array<{ id, tool_name, scope_key, scope_value, label, created_at }>}
 */
export function listForTool(toolName) {
  return getDb().prepare(
    'SELECT id, tool_name, scope_key, scope_value, label, created_at FROM tool_allowlists WHERE tool_name = ? ORDER BY created_at ASC'
  ).all(toolName);
}

/**
 * List all allowlist entries grouped by tool.
 *
 * @returns {Record<string, Array<{ id, tool_name, scope_key, scope_value, label, created_at }>>}
 */
export function listAll() {
  const rows = getDb().prepare(
    'SELECT id, tool_name, scope_key, scope_value, label, created_at FROM tool_allowlists ORDER BY tool_name ASC, created_at ASC'
  ).all();

  const grouped = {};
  for (const row of rows) {
    if (!grouped[row.tool_name]) grouped[row.tool_name] = [];
    grouped[row.tool_name].push(row);
  }
  return grouped;
}
