/**
 * Printing Press security-mode resolver.
 *
 * One env var controls the security posture for every printing_press_* tool:
 *
 *   MADARX_PRINTING_PRESS_SECURITY_MODE = strict | balanced | permissive
 *
 * Modes (high level — see docs/integrations/printing-press-security-modes.md):
 *
 *   strict (default)
 *     - Approval required for every call regardless of trust mode.
 *     - Allowlist default-deny on all run/query tools.
 *     - Generated CLIs declaring write scopes are refused outright.
 *     - MCP server output never mounted into claude-code spawns.
 *     - Rate-limit multiplier 1.0× (strictest).
 *     - Allowed in NODE_ENV=production.
 *
 *   balanced
 *     - Trust-mode rules apply (auto bypasses high/medium gates).
 *     - Allowlist default-deny.
 *     - Network-writing CLIs allowed when allowlisted.
 *     - MCP mount allowed.
 *     - Rate-limit multiplier 2.0×.
 *     - Allowed in production but emits boot warning.
 *
 *   permissive
 *     - Auto-approve for medium/high (irreversible_external still gated — this
 *       is a load-bearing invariant of the registry and not overridable here).
 *     - Allowlist default-allow.
 *     - MCP mount allowed.
 *     - Rate-limit multiplier 5.0×.
 *     - REFUSED in NODE_ENV=production at boot (exit 1).
 *
 * The resolver is intentionally pure: it reads env + config, returns a frozen
 * capabilities object, and never touches the DB. Side-effect-producing flips
 * live in the API route (writes the audit + printing_press_security_events).
 */

import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';

export const PRINTING_PRESS_MODES = Object.freeze(['strict', 'balanced', 'permissive']);
export const PRINTING_PRESS_MODE_DEFAULT = 'strict';
export const PRINTING_PRESS_ENV_VAR = 'MADARX_PRINTING_PRESS_SECURITY_MODE';

const _warnedUnknown = new Set();

export const PRINTING_PRESS_MODE_OVERRIDE_CONFIG_KEY = 'printing_press_security_mode_override';

/**
 * Read the DB-stored mode override written by PATCH /api/printing-press/security-mode.
 * Opens a short-lived read-only handle so this module never imports lib/db.js
 * (which would create a circular: db → seeds → registry → security → db).
 * Returns null on any failure (DB not ready, table missing, etc.) — non-fatal.
 */
let _cachedOverride = null;
let _cachedOverrideAt = 0;
const OVERRIDE_TTL_MS = 1500;
function readDbOverride() {
  const now = Date.now();
  if (now - _cachedOverrideAt < OVERRIDE_TTL_MS) return _cachedOverride;
  try {
    const override = process.env.MADARX_DATA_DIR || process.env.VILLA515_DATA_DIR;
    const root = override
      ? path.resolve(override)
      : process.env.MADARX_HOME
        ? path.join(path.resolve(process.env.MADARX_HOME), 'data')
        : path.resolve(process.cwd(), 'data');
    const dbPath = path.join(root, 'madarx.sqlite');
    const db = new DatabaseSync(dbPath, { readOnly: true });
    try {
      const row = db.prepare('SELECT value FROM config WHERE key = ?').get(PRINTING_PRESS_MODE_OVERRIDE_CONFIG_KEY);
      _cachedOverride = row?.value || null;
    } finally {
      db.close();
    }
  } catch {
    _cachedOverride = null;
  }
  _cachedOverrideAt = now;
  return _cachedOverride;
}

/**
 * Invalidate the DB override cache so the next resolveMode() reads fresh.
 * Called by the security-mode PATCH route after a successful flip.
 */
export function invalidateModeCache() {
  _cachedOverrideAt = 0;
  _cachedOverride = null;
}

/**
 * Resolve the active security mode. Precedence:
 *   1. explicit envOverride argument (used by tests + boot guard)
 *   2. DB override row (set via /api/printing-press/security-mode PATCH)
 *   3. process.env[MADARX_PRINTING_PRESS_SECURITY_MODE]
 *   4. PRINTING_PRESS_MODE_DEFAULT ('strict')
 *
 * Unknown values fall back to strict and emit a one-time warning.
 */
export function resolveMode(envOverride) {
  const raw = envOverride
    ?? readDbOverride()
    ?? process.env[PRINTING_PRESS_ENV_VAR];
  if (!raw) return PRINTING_PRESS_MODE_DEFAULT;
  const normalized = String(raw).trim().toLowerCase();
  if (PRINTING_PRESS_MODES.includes(normalized)) return normalized;
  if (!_warnedUnknown.has(normalized)) {
    _warnedUnknown.add(normalized);
    console.warn(
      `[printing-press] Unknown ${PRINTING_PRESS_ENV_VAR}="${raw}" — defaulting to "${PRINTING_PRESS_MODE_DEFAULT}".`
    );
  }
  return PRINTING_PRESS_MODE_DEFAULT;
}

/**
 * Capabilities for a given mode. The structure is stable — callers
 * destructure these fields. Adding new fields is backwards-compatible;
 * renaming requires a coordinated change in the registry + adapter.
 */
export function capabilitiesFor(mode) {
  const m = PRINTING_PRESS_MODES.includes(mode) ? mode : PRINTING_PRESS_MODE_DEFAULT;
  if (m === 'strict') {
    return Object.freeze({
      mode: 'strict',
      // Force approval on every printing_press_* call even when trust=auto.
      force_approval: true,
      // tool_allowlists default-allow flag for printing_press_run / _query
      allowlist_default_allow: false,
      // Refuse generated CLIs declaring write scopes
      allow_network_write_clis: false,
      // Skip mounting MCP servers into claude-code spawn args
      allow_mcp_mount: false,
      // Multiplier applied to rate-limit max; floor of 1
      rate_limit_multiplier: 1.0,
      // Whether the mode may run in NODE_ENV=production
      production_safe: true,
      // Whether boot should emit a warning when this mode is active in prod
      warn_in_production: false,
      // Whether allow_install_at_runtime (i.e. POST /api/printing-press/install
      // is permitted without an explicit confirm flag). Always true — the
      // registry's irreversible_external gate is the actual barrier.
      allow_install_at_runtime: true,
      // UI banner colour: 'red' | 'amber' | 'grey'
      banner: 'red',
      banner_message: 'Strict mode: every Printing Press action requires explicit approval.',
    });
  }
  if (m === 'balanced') {
    return Object.freeze({
      mode: 'balanced',
      force_approval: false,
      allowlist_default_allow: false,
      allow_network_write_clis: true,
      allow_mcp_mount: true,
      rate_limit_multiplier: 2.0,
      production_safe: true,
      warn_in_production: true,
      allow_install_at_runtime: true,
      banner: 'amber',
      banner_message: 'Balanced mode: trust-mode rules apply. Allowlist still default-deny.',
    });
  }
  // permissive
  return Object.freeze({
    mode: 'permissive',
    force_approval: false,
    allowlist_default_allow: true,
    allow_network_write_clis: true,
    allow_mcp_mount: true,
    rate_limit_multiplier: 5.0,
    production_safe: false,
    warn_in_production: true,
    allow_install_at_runtime: true,
    banner: 'grey',
    banner_message: 'Permissive mode: only suitable for sandboxes. Not allowed in production.',
  });
}

/**
 * Convenience: current capabilities snapshot.
 */
export function currentCapabilities() {
  return capabilitiesFor(resolveMode());
}

/**
 * Boot-time guard. Call from a check script (mirrors npm run check:disk-enc).
 * Returns { ok: true } when the mode is allowed, or { ok: false, exit_code, message }
 * when the runtime should refuse to start.
 */
export function isProductionSafe(mode = resolveMode(), nodeEnv = process.env.NODE_ENV) {
  const caps = capabilitiesFor(mode);
  if (nodeEnv !== 'production') return { ok: true };
  if (!caps.production_safe) {
    return {
      ok: false,
      exit_code: 1,
      message:
        `[printing-press] ${PRINTING_PRESS_ENV_VAR}="${caps.mode}" is not allowed in NODE_ENV=production. ` +
        `Set ${PRINTING_PRESS_ENV_VAR}=strict (or balanced) before booting.`,
    };
  }
  if (caps.warn_in_production) {
    return {
      ok: true,
      warning: `[printing-press] Running in ${caps.mode} mode in production. ${caps.banner_message}`,
    };
  }
  return { ok: true };
}

/**
 * Apply the mode's rate-limit multiplier to a base { window_seconds, max }
 * descriptor. Used by the registry executor's rate-limit gate. Returns a new
 * object — never mutates the input.
 */
export function applyRateLimitMultiplier(baseRateLimit, mode = resolveMode()) {
  if (!baseRateLimit) return null;
  const caps = capabilitiesFor(mode);
  const max = Math.max(1, Math.round(baseRateLimit.max * caps.rate_limit_multiplier));
  return { ...baseRateLimit, max };
}
