/**
 * SSH HTTP layer — Phase 5 of the SSH tool integration plan
 * (docs/superpowers/plans/2026-05-12-ssh-tool-integration.md).
 *
 * Two router factories:
 *   createSshUserRouter — `/api/ssh/connections` + `/api/ssh/grants`
 *   createSshAdminRouter — `/api/ssh/admin/*`
 *
 * Both are mounted from src/bridge/server.ts ONLY when `ssh.enabled=true`
 * AND `MCP_ENCRYPTION_KEY` is configured (the master key gates the envelope-
 * encrypted DEKs in Phase 1a). When SSH is disabled, no router is mounted —
 * the endpoints simply do not exist (no 404 distinction from a missing route).
 *
 * Design conventions:
 * - Manual JSON validation (no zod) for consistency with pieces-api / mcp-api.
 * - Reason gating: every admin write path requires `body.reason` >= 8 chars.
 * - Audit logging: every admin write uses beginAudit → action → completeAudit
 *   (or beginAndComplete for synchronous DB-only changes); user actions audit
 *   only the privileged ones (test, verify, replace).
 * - Maintenance mode: when active, all write endpoints — user AND admin —
 *   return 503 with `Retry-After: 30`. Read endpoints stay available.
 * - Buffer hygiene: decrypted private keys are passed to ssh-session and then
 *   cleared on every code path (success, error, validation failure).
 * - Response shaping: `presentConnection()` strips encrypted blob fields and
 *   the pending host-key token (token only returned on test / observation).
 */

import { Router, type Request, type Response, type RequestHandler } from 'express';
import type Database from 'better-sqlite3';

import type { SshConnection, SshConnectionRepo, HostKeyVerifyResult } from '../ssh/connection-repo.js';
import type { SshGrant, SshGrantsRepo, SshGrantSubjectType } from '../ssh/grants-repo.js';
import type { SshAuditRepo, SshAuditRow } from '../ssh/audit-repo.js';
import type { SshAbuseRepo } from '../ssh/abuse-repo.js';
import type { SshAccessResolver } from '../ssh/access.js';
import type { MaintenanceController } from '../ssh/maintenance.js';
import type { AdminRateLimiter } from '../ssh/admin-rate-limit.js';
import { logger } from '../logger.js';

// ──────────────────────────────────────────────────────────────────────
// Types
// ──────────────────────────────────────────────────────────────────────

export interface SshTesterArgs {
  connection: SshConnection;
  decryptedKey: Buffer;
  passphrase: Buffer | null;
  timeoutMs: number;
}

export type SshTesterVerdict = 'first_observe' | 'mismatch' | 'pass' | 'alg_not_allowed';

export interface SshTesterResult {
  /** SHA256 fingerprint of the observed host key. */
  fingerprint: string;
  /** Base64 of the observed host key (wire format). */
  hostKeyB64: string;
  hostKeyType: string;
  verdict: SshTesterVerdict;
}

export interface SshTester {
  test(args: SshTesterArgs): Promise<SshTesterResult>;
}

export interface SshEncryptResult {
  blob: Buffer;
  passphraseBlob: Buffer | null;
  keyVersion: number;
  fingerprint: string;
  publicKey: string;
}

export interface SshApiDeps {
  db: Database.Database;
  requireAuth: RequestHandler;
  requireAdmin: RequestHandler;
  getUserId(req: Request): string | null;
  isAdmin(req: Request): boolean;
  getOrgIds(req: Request): string[];

  connectionRepo: SshConnectionRepo;
  grantsRepo: SshGrantsRepo;
  auditRepo: SshAuditRepo;
  abuseRepo: SshAbuseRepo;
  accessResolver: SshAccessResolver;

  maintenance: MaintenanceController;
  forceUnlockLimiter: AdminRateLimiter;

  /**
   * Encrypt a PEM (and optional passphrase) into the envelope-encrypted blobs
   * stored in ssh_connections. ownerId=null = system DEK (global connection),
   * ownerId=<uid> = user DEK.
   */
  encryptKeyMaterial(ownerId: string | null, pem: Buffer, passphrase: Buffer | null): SshEncryptResult;

  /** Decrypt blob using the correct DEK. Buffer must be cleared by caller. */
  decryptKeyMaterial(ownerId: string | null, blob: Buffer): Buffer;
  decryptPassphrase(ownerId: string | null, blob: Buffer | null): Buffer | null;

  /**
   * Generate a fresh keypair (no passphrase). The caller is expected to feed
   * the returned `privateKeyPem` back through `encryptKeyMaterial` for at-rest
   * encryption.
   */
  generateKeypair(keyType: 'ed25519' | 'rsa-4096'): {
    privateKeyPem: Buffer;
    publicKey: string;
  };

  /**
   * Derive the OpenSSH-format public key (`<algo> <base64>`) from the stored
   * private-key blob. Handles decrypt + format + buffer-clear internally.
   */
  derivePublicKey(
    ownerId: string | null,
    blob: Buffer,
    passphraseBlob: Buffer | null,
  ): string;

  /** SSH dial helper — injected so tests don't need a real SSH server. */
  sshTester: SshTester;

  /** Connection test timeout. Defaults to 5s. */
  connectionTestTimeoutMs?: number;

  /**
   * Optional hook called after a grant is revoked / deleted so the caller can
   * kick any active WebSocket viewers that depended on that grant. The hook
   * is a no-op pass-through (returns 0) when the SSH console subsystem isn't
   * wired (e.g. tests, or `ssh.console.enabled=false`).
   *
   * Implementation typically calls `SessionRegistry.revokeAccessFor`.
   */
  onAccessRevoked?: (args: { connectionId: string; userId: string }) => number | void;
}

// ──────────────────────────────────────────────────────────────────────
// Helpers
// ──────────────────────────────────────────────────────────────────────

const REASON_MIN_CHARS = 8;

function validateReason(reason: unknown): string | null {
  if (typeof reason !== 'string') return 'reason is required (string >= 8 chars)';
  if (reason.length < REASON_MIN_CHARS) return `reason must be at least ${REASON_MIN_CHARS} characters`;
  return null;
}

function maintenance503(maintenance: MaintenanceController, res: Response): boolean {
  if (!maintenance.isActive()) return false;
  res.setHeader('Retry-After', '30');
  res.status(503).json({
    error: 'rotation_in_progress',
    detail: 'SSH subsystem is in maintenance — try again shortly.',
    snapshot: maintenance.snapshot(),
  });
  return true;
}

function safePort(p: unknown): number | null {
  if (typeof p !== 'number' || !Number.isInteger(p) || p < 1 || p > 65535) return null;
  return p;
}

function safeString(v: unknown, max = 1024): string | null {
  if (typeof v !== 'string') return null;
  if (v.length === 0 || v.length > max) return null;
  return v;
}

function safeOptionalString(v: unknown, max = 1024): string | null | undefined {
  if (v === undefined || v === null) return undefined;
  if (typeof v !== 'string') return null;
  if (v.length > max) return null;
  return v;
}

function safePathPrefix(v: unknown): string | null {
  const s = safeString(v, 1024);
  if (s === null) return null;
  if (s.length === 0) return null;
  // Reject any `..` parent-ref segment using either separator. POSIX and
  // Windows-style prefixes (drive letters, UNC, no-leading-slash) are
  // all allowed; the runtime validateRemotePath enforces containment.
  const segments = s.split(/[\\/]/);
  if (segments.includes('..')) return null;
  return s;
}

function safeFingerprint(v: unknown): string | null {
  if (typeof v !== 'string') return null;
  // SHA256:<base64> — base64 chars + ='s, ~46 chars typical.
  if (!/^SHA256:[A-Za-z0-9+/=]{20,80}$/.test(v)) return null;
  return v;
}

function safeUuid(v: unknown): string | null {
  if (typeof v !== 'string') return null;
  if (!/^[a-f0-9-]{8,}$/.test(v)) return null;
  return v;
}

function safeExpiresAt(v: unknown): string | null | undefined {
  if (v === undefined || v === null) return undefined;
  if (typeof v !== 'string') return null;
  // ISO8601 — Date.parse returns NaN for invalid strings.
  const t = Date.parse(v);
  if (Number.isNaN(t)) return null;
  return new Date(t).toISOString();
}

function safeSubjectType(v: unknown): SshGrantSubjectType | null {
  return v === 'user' || v === 'org' ? v : null;
}

function presentConnection(conn: SshConnection): Record<string, unknown> {
  // Strip the encrypted blob fields and the pending verify token (the token
  // is only surfaced on /test responses; subsequent GETs do not expose it
  // again — the user must hold onto it).
  return {
    id: conn.id,
    ownerId: conn.ownerId,
    label: conn.label,
    host: conn.host,
    port: conn.port,
    username: conn.username,
    keyVersion: conn.keyVersion,
    keyFingerprint: conn.keyFingerprint,
    hostKeyType: conn.hostKeyType,
    hostKeyFingerprint: conn.hostKeyFingerprint,
    hostKeyRecordedAt: conn.hostKeyRecordedAt,
    hostKeyVerifiedAt: conn.hostKeyVerifiedAt,
    hostKeyPending: conn.hostKeyPending,
    hostKeyPendingFingerprint: conn.hostKeyPendingFingerprint,
    hostKeyPendingSource: conn.hostKeyPendingSource,
    commandDenyPatterns: conn.commandDenyPatterns,
    commandAllowPatterns: conn.commandAllowPatterns,
    remotePathPrefix: conn.remotePathPrefix,
    allowRemoteUnrestricted: conn.allowRemoteUnrestricted,
    allowPrivateAddresses: conn.allowPrivateAddresses,
    enabled: conn.enabled,
    disabledByAdmin: conn.disabledByAdmin,
    disabledByAdminReason: conn.disabledByAdminReason,
    disabledByAdminAt: conn.disabledByAdminAt,
    disabledByAdminUserId: conn.disabledByAdminUserId,
    createdAt: conn.createdAt,
    updatedAt: conn.updatedAt,
  };
}

function presentGrant(g: SshGrant): Record<string, unknown> {
  return { ...g };
}

function presentAuditRow(a: SshAuditRow): Record<string, unknown> {
  return { ...a };
}

function safeLimit(v: unknown, def = 50, max = 500): number {
  if (typeof v === 'string') {
    const n = Number(v);
    if (Number.isInteger(n) && n > 0 && n <= max) return n;
  }
  if (typeof v === 'number' && Number.isInteger(v) && v > 0 && v <= max) return v;
  return def;
}

function jsonError(res: Response, status: number, error: string, detail?: unknown): void {
  if (detail === undefined) {
    res.status(status).json({ error });
  } else {
    res.status(status).json({ error, detail });
  }
}

// ──────────────────────────────────────────────────────────────────────
// User router — /api/ssh/connections + /api/ssh/grants
// ──────────────────────────────────────────────────────────────────────

export function createSshUserRouter(deps: SshApiDeps): Router {
  const router = Router();
  const testTimeoutMs = deps.connectionTestTimeoutMs ?? 5000;

  // GET /api/ssh/connections — list owned (and globals visible via grant).
  // For Phase 5 we return:
  //   - all owned (owner_id == userId)
  //   - all globals (owner_id IS NULL) — visibility is enforced at tool-call
  //     time via accessResolver, so listing globals here is informational.
  // Admins see all via /api/ssh/admin/connections (separate endpoint).
  router.get('/connections', deps.requireAuth, (req, res) => {
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const owned = deps.connectionRepo.listOwned(userId);
      const all = deps.connectionRepo.listAll();
      const globals = all.filter((c) => c.ownerId === null);
      // Deduplicate (owned should never overlap globals; safety net).
      const seen = new Set<string>(owned.map((c) => c.id));
      const list = [...owned];
      for (const g of globals) {
        if (!seen.has(g.id)) { list.push(g); seen.add(g.id); }
      }
      res.json({ connections: list.map(presentConnection) });
    } catch (e) {
      logger.warn(`[ssh:api] list user connections failed err=${String(e)}`);
      jsonError(res, 500, 'list_failed');
    }
  });

  // POST /api/ssh/connections — create user-owned.
  router.post('/connections', deps.requireAuth, async (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const body = req.body ?? {};
      const label = safeString(body.label, 200);
      const host = safeString(body.host, 255);
      const port = safePort(body.port);
      const username = safeString(body.username, 64);
      const remotePathPrefix = safePathPrefix(body.remotePathPrefix);
      const denyPatterns = safeOptionalString(body.commandDenyPatterns, 4096);
      const allowPatterns = safeOptionalString(body.commandAllowPatterns, 4096);
      // User-owned create rejects admin-only flags up front.
      if (body.allowRemoteUnrestricted === true || body.allowRemoteUnrestricted === 1) {
        jsonError(res, 403, 'allow_remote_unrestricted_admin_only'); return;
      }
      if (body.allowPrivateAddresses === true || body.allowPrivateAddresses === 1) {
        jsonError(res, 403, 'allow_private_addresses_admin_only'); return;
      }

      // Keypair source: 'provided' (user uploads PEM, default) or
      // 'generate' (orchestrator creates a fresh keypair). In the latter case
      // the user gets the public key back exactly once in this response.
      const keypairSource = body.keypairSource === 'generate' ? 'generate' : 'provided';
      let pemBuf: Buffer;
      let passBuf: Buffer | null;
      if (keypairSource === 'generate') {
        const keyType = body.generateKeyType === 'rsa-4096' ? 'rsa-4096' : 'ed25519';
        const generated = deps.generateKeypair(keyType);
        pemBuf = generated.privateKeyPem;
        passBuf = null;
      } else {
        const privateKey = typeof body.privateKeyPem === 'string' ? body.privateKeyPem : null;
        const passphrase = typeof body.passphrase === 'string' ? body.passphrase : null;
        if (!privateKey) {
          jsonError(res, 400, 'invalid_input', {
            required: ['label', 'host', 'port (1-65535)', 'username', 'privateKeyPem', 'remotePathPrefix'],
          });
          return;
        }
        pemBuf = Buffer.from(privateKey, 'utf8');
        passBuf = passphrase ? Buffer.from(passphrase, 'utf8') : null;
      }

      if (!label || !host || port === null || !username || !remotePathPrefix) {
        pemBuf.fill(0);
        if (passBuf) passBuf.fill(0);
        jsonError(res, 400, 'invalid_input', {
          required: ['label', 'host', 'port (1-65535)', 'username', 'privateKeyPem', 'remotePathPrefix'],
        });
        return;
      }
      if (denyPatterns === null) {
        pemBuf.fill(0);
        if (passBuf) passBuf.fill(0);
        jsonError(res, 400, 'invalid_command_deny_patterns'); return;
      }
      if (allowPatterns === null) {
        pemBuf.fill(0);
        if (passBuf) passBuf.fill(0);
        jsonError(res, 400, 'invalid_command_allow_patterns'); return;
      }

      let encrypted: SshEncryptResult;
      try {
        encrypted = deps.encryptKeyMaterial(userId, pemBuf, passBuf);
      } finally {
        pemBuf.fill(0);
        if (passBuf) passBuf.fill(0);
      }
      const conn = deps.connectionRepo.create({
        ownerId: userId,
        label, host, port, username,
        privateKeyEnc: encrypted.blob,
        passphraseEnc: encrypted.passphraseBlob,
        keyVersion: encrypted.keyVersion,
        keyFingerprint: encrypted.fingerprint,
        remotePathPrefix,
        commandDenyPatterns: denyPatterns ?? null,
        commandAllowPatterns: allowPatterns ?? null,
      });
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.upsert',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: userId,
          actingUserId: userId,
          detail: { op: 'create', label: conn.label, host: conn.host, port: conn.port, keypairSource },
        },
        'success',
      );
      res.status(201).json({
        connection: presentConnection(conn),
        publicKey: encrypted.publicKey,
      });
    } catch (e) {
      logger.warn(`[ssh:api] create user connection failed err=${String(e)}`);
      jsonError(res, 500, 'create_failed');
    }
  });

  router.get('/connections/:id', deps.requireAuth, (req, res) => {
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      // Visibility: owner, admin, or global (info-only). Other users see 404
      // (do not leak existence of someone else's user-owned connection).
      if (conn.ownerId !== null && conn.ownerId !== userId && !deps.isAdmin(req)) {
        jsonError(res, 404, 'not_found'); return;
      }
      let publicKey: string | null = null;
      try {
        publicKey = deps.derivePublicKey(conn.ownerId, conn.privateKeyEnc, conn.passphraseEnc);
      } catch (e) {
        // Don't fail the whole response if key derivation fails (e.g. corrupt
        // blob, missing passphrase) — return the connection without it and log.
        logger.warn(`[ssh:api] derive public key failed id=${conn.id} err=${String(e)}`);
      }
      res.json({ connection: presentConnection(conn), publicKey });
    } catch (e) {
      logger.warn(`[ssh:api] get user connection failed err=${String(e)}`);
      jsonError(res, 500, 'get_failed');
    }
  });

  router.patch('/connections/:id', deps.requireAuth, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      // Only owner may PATCH user-owned. Admin PATCHes globals via admin route.
      if (conn.ownerId !== userId) { jsonError(res, 403, 'owner_only'); return; }
      const body = req.body ?? {};

      const patch: Parameters<SshConnectionRepo['update']>[1] = {};
      if (body.label !== undefined) {
        const v = safeString(body.label, 200);
        if (v === null) { jsonError(res, 400, 'invalid_label'); return; }
        patch.label = v;
      }
      if (body.host !== undefined) {
        const v = safeString(body.host, 255);
        if (v === null) { jsonError(res, 400, 'invalid_host'); return; }
        patch.host = v;
      }
      if (body.port !== undefined) {
        const v = safePort(body.port);
        if (v === null) { jsonError(res, 400, 'invalid_port'); return; }
        patch.port = v;
      }
      if (body.username !== undefined) {
        const v = safeString(body.username, 64);
        if (v === null) { jsonError(res, 400, 'invalid_username'); return; }
        patch.username = v;
      }
      if (body.remotePathPrefix !== undefined) {
        const v = safePathPrefix(body.remotePathPrefix);
        if (v === null) { jsonError(res, 400, 'invalid_remote_path_prefix'); return; }
        patch.remotePathPrefix = v;
      }
      if (body.commandDenyPatterns !== undefined) {
        const v = safeOptionalString(body.commandDenyPatterns, 4096);
        if (v === null) { jsonError(res, 400, 'invalid_command_deny_patterns'); return; }
        patch.commandDenyPatterns = v ?? null;
      }
      if (body.commandAllowPatterns !== undefined) {
        const v = safeOptionalString(body.commandAllowPatterns, 4096);
        if (v === null) { jsonError(res, 400, 'invalid_command_allow_patterns'); return; }
        patch.commandAllowPatterns = v ?? null;
      }
      // Users cannot toggle admin-only flags.
      if (body.allowRemoteUnrestricted !== undefined || body.allowPrivateAddresses !== undefined) {
        jsonError(res, 403, 'admin_only_flag'); return;
      }
      // Key rotation (privateKeyPem in patch). Re-encrypt and bump key_version.
      if (typeof body.privateKeyPem === 'string') {
        let pemBuf = Buffer.from(body.privateKeyPem, 'utf8');
        let passBuf = typeof body.passphrase === 'string' ? Buffer.from(body.passphrase, 'utf8') : null;
        try {
          const enc = deps.encryptKeyMaterial(userId, pemBuf, passBuf);
          patch.privateKeyEnc = enc.blob;
          patch.passphraseEnc = enc.passphraseBlob;
          patch.keyVersion = enc.keyVersion;
          patch.keyFingerprint = enc.fingerprint;
        } finally {
          pemBuf.fill(0);
          if (passBuf) passBuf.fill(0);
        }
      }
      const ok = deps.connectionRepo.update(conn.id, patch);
      if (!ok) { jsonError(res, 404, 'not_found'); return; }
      const updated = deps.connectionRepo.resolveConnection(conn.id);
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.upsert',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: userId,
          actingUserId: userId,
          detail: { op: 'update', fields: Object.keys(patch) },
        },
        'success',
      );
      res.json({ connection: presentConnection(updated!) });
    } catch (e) {
      logger.warn(`[ssh:api] patch user connection failed err=${String(e)}`);
      jsonError(res, 500, 'update_failed');
    }
  });

  router.delete('/connections/:id', deps.requireAuth, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== userId) { jsonError(res, 403, 'owner_only'); return; }
      // Begin audit BEFORE delete so FK(connection_id) is still valid.
      // FK has ON DELETE SET NULL; complete() updates by audit_id only.
      const auditId = deps.auditRepo.begin({
        action: 'ssh.connection.delete',
        entityType: 'ssh_connection',
        entityId: conn.id,
        connectionId: conn.id,
        ownerId: userId,
        actingUserId: userId,
        detail: { label: conn.label, host: conn.host },
      });
      const ok = deps.connectionRepo.delete(conn.id);
      if (!ok) {
        deps.auditRepo.complete(auditId, 'failed', { err: 'no_changes' });
        jsonError(res, 404, 'not_found');
        return;
      }
      deps.auditRepo.complete(auditId, 'success');
      res.json({ ok: true });
    } catch (e) {
      logger.warn(`[ssh:api] delete user connection failed err=${String(e)}`);
      jsonError(res, 500, 'delete_failed');
    }
  });

  // POST /api/ssh/connections/:id/test — capture-only connect. Decrypts the
  // key, dials the host (or asks the injected tester to do so), captures the
  // host key, and stores it pending with a fresh verify token. Returns the
  // fingerprint + token to the user so they can call /verify-host-key next.
  router.post('/connections/:id/test', deps.requireAuth, async (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    const userId = deps.getUserId(req);
    if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
    const conn = deps.connectionRepo.resolveConnection(req.params.id);
    if (!conn) { jsonError(res, 404, 'not_found'); return; }
    if (conn.ownerId !== userId && !deps.isAdmin(req)) {
      jsonError(res, 403, 'owner_or_admin_only'); return;
    }
    let decryptedKey: Buffer | null = null;
    let passphrase: Buffer | null = null;
    let auditId: number | null = null;
    try {
      auditId = deps.auditRepo.begin({
        action: 'ssh.connection.host_key.tofu_record',
        entityType: 'ssh_connection',
        entityId: conn.id,
        connectionId: conn.id,
        ownerId: conn.ownerId,
        actingUserId: userId,
        detail: { op: 'test' },
      });
      decryptedKey = deps.decryptKeyMaterial(conn.ownerId, conn.privateKeyEnc);
      passphrase = deps.decryptPassphrase(conn.ownerId, conn.passphraseEnc);
      const result = await deps.sshTester.test({
        connection: conn,
        decryptedKey,
        passphrase,
        timeoutMs: testTimeoutMs,
      });
      // alg_not_allowed: the observed host key uses a banned algorithm
      // (e.g. ssh-rsa with SHA1). Don't store as pending — we'd never accept it.
      // Surface the fingerprint so the operator can audit/replace the server's key.
      if (result.verdict === 'alg_not_allowed') {
        deps.auditRepo.complete(auditId, 'denied', {
          verdict: result.verdict,
          observedFingerprint: result.fingerprint,
          hostKeyType: result.hostKeyType,
        });
        res.status(502).json({
          error: 'host_key_alg_not_allowed',
          verdict: result.verdict,
          fingerprint: result.fingerprint,
          hostKeyType: result.hostKeyType,
        });
        return;
      }
      // Store the observation as pending for first_observe and mismatch.
      // 'pass' means the key already matched the verified record — no token needed.
      let token: string | null = null;
      if (result.verdict === 'first_observe' || result.verdict === 'mismatch') {
        const stored = deps.connectionRepo.setHostKeyPendingWithToken(
          conn.id,
          result.hostKeyB64,
          result.fingerprint,
          result.verdict === 'first_observe' ? 'tofu_record' : 'mismatch',
        );
        token = stored?.token ?? null;
      }
      deps.auditRepo.complete(auditId, 'success', {
        verdict: result.verdict,
        observedFingerprint: result.fingerprint,
      });
      res.json({
        verdict: result.verdict,
        fingerprint: result.fingerprint,
        hostKeyType: result.hostKeyType,
        pendingToken: token,
      });
    } catch (e) {
      if (auditId !== null) {
        deps.auditRepo.complete(auditId, 'failed', { err: String(e) });
      }
      logger.warn(`[ssh:api] connection test failed id=${conn.id} err=${String(e)}`);
      jsonError(res, 502, 'test_failed', { detail: String(e) });
    } finally {
      if (decryptedKey) decryptedKey.fill(0);
      if (passphrase) passphrase.fill(0);
    }
  });

  // POST /api/ssh/connections/:id/verify-host-key
  router.post('/connections/:id/verify-host-key', deps.requireAuth, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== userId && !deps.isAdmin(req)) {
        jsonError(res, 403, 'owner_or_admin_only'); return;
      }
      const body = req.body ?? {};
      const fingerprint = safeFingerprint(body.fingerprint);
      const token = safeUuid(body.token);
      if (!fingerprint || !token) {
        jsonError(res, 400, 'invalid_input', { required: ['fingerprint', 'token'] });
        return;
      }
      const result: HostKeyVerifyResult = deps.connectionRepo.setHostKeyVerified(conn.id, token, fingerprint);
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.host_key.verify',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          detail: { result, fingerprint },
        },
        result === 'verified' ? 'success' : 'failed',
      );
      if (result === 'verified') {
        const updated = deps.connectionRepo.resolveConnection(conn.id)!;
        res.json({ ok: true, connection: presentConnection(updated) });
      } else {
        jsonError(res, 409, result);
      }
    } catch (e) {
      logger.warn(`[ssh:api] verify host key failed err=${String(e)}`);
      jsonError(res, 500, 'verify_failed');
    }
  });

  // POST /api/ssh/connections/:id/replace-host-key — rotate an already-verified key.
  router.post('/connections/:id/replace-host-key', deps.requireAuth, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== userId && !deps.isAdmin(req)) {
        jsonError(res, 403, 'owner_or_admin_only'); return;
      }
      const body = req.body ?? {};
      const fingerprint = safeFingerprint(body.fingerprint);
      const token = safeUuid(body.token);
      const reasonErr = validateReason(body.reason);
      if (!fingerprint || !token) {
        jsonError(res, 400, 'invalid_input', { required: ['fingerprint', 'token', 'reason'] });
        return;
      }
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const result: HostKeyVerifyResult = deps.connectionRepo.replaceHostKey(conn.id, token, fingerprint);
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.host_key.replace',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          reason: String(body.reason),
          detail: { result, fingerprint },
        },
        result === 'verified' ? 'success' : 'failed',
      );
      if (result === 'verified') {
        const updated = deps.connectionRepo.resolveConnection(conn.id)!;
        res.json({ ok: true, connection: presentConnection(updated) });
      } else {
        jsonError(res, 409, result);
      }
    } catch (e) {
      logger.warn(`[ssh:api] replace host key failed err=${String(e)}`);
      jsonError(res, 500, 'replace_failed');
    }
  });

  router.get('/connections/:id/audit', deps.requireAuth, (req, res) => {
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== userId && !deps.isAdmin(req)) {
        jsonError(res, 403, 'owner_or_admin_only'); return;
      }
      const limit = safeLimit(req.query.limit);
      const rows = deps.auditRepo.listForConnection(conn.id, limit);
      res.json({ audit: rows.map(presentAuditRow) });
    } catch (e) {
      logger.warn(`[ssh:api] audit list failed err=${String(e)}`);
      jsonError(res, 500, 'audit_failed');
    }
  });

  // GET /api/ssh/grants/visible-to-me
  router.get('/grants/visible-to-me', deps.requireAuth, (req, res) => {
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const orgIds = deps.getOrgIds(req);
      const orgPh = orgIds.length > 0 ? orgIds.map(() => '?').join(',') : null;
      const sql = `
        SELECT * FROM ssh_connection_grants
        WHERE (subject_type = 'user' AND subject_id = ?)
          ${orgPh ? `OR (subject_type = 'org' AND subject_id IN (${orgPh}))` : ''}
        ORDER BY created_at DESC
      `;
      const params: unknown[] = [userId];
      if (orgPh) params.push(...orgIds);
      const rows = deps.db.prepare(sql).all(...params) as Array<{
        id: string; connection_id: string; subject_type: SshGrantSubjectType; subject_id: string;
        piece_name: string | null; applies_to_all_pieces: number; granted_by_user_id: string;
        reason: string; expires_at: string | null; created_at: string;
      }>;
      const grants = rows.map((r) => ({
        id: r.id,
        connectionId: r.connection_id,
        subjectType: r.subject_type,
        subjectId: r.subject_id,
        pieceName: r.piece_name,
        appliesToAllPieces: r.applies_to_all_pieces === 1,
        grantedByUserId: r.granted_by_user_id,
        reason: r.reason,
        expiresAt: r.expires_at,
        createdAt: r.created_at,
      }));
      res.json({ grants });
    } catch (e) {
      logger.warn(`[ssh:api] list visible grants failed err=${String(e)}`);
      jsonError(res, 500, 'list_grants_failed');
    }
  });

  return router;
}

// ──────────────────────────────────────────────────────────────────────
// Admin router — /api/ssh/admin/*
// ──────────────────────────────────────────────────────────────────────

export function createSshAdminRouter(deps: SshApiDeps): Router {
  const router = Router();

  // GET /api/ssh/admin/connections
  router.get('/connections', deps.requireAdmin, (_req, res) => {
    try {
      const list = deps.connectionRepo.listAll();
      res.json({ connections: list.map(presentConnection) });
    } catch (e) {
      logger.warn(`[ssh:api] admin list connections failed err=${String(e)}`);
      jsonError(res, 500, 'list_failed');
    }
  });

  router.get('/connections/:id', deps.requireAdmin, (req, res) => {
    try {
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      let publicKey: string | null = null;
      try {
        publicKey = deps.derivePublicKey(conn.ownerId, conn.privateKeyEnc, conn.passphraseEnc);
      } catch (e) {
        logger.warn(`[ssh:api] admin derive public key failed id=${conn.id} err=${String(e)}`);
      }
      res.json({ connection: presentConnection(conn), publicKey });
    } catch (e) {
      logger.warn(`[ssh:api] admin get connection failed err=${String(e)}`);
      jsonError(res, 500, 'get_failed');
    }
  });

  router.patch('/connections/:id/disable', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      const ok = deps.connectionRepo.disableByAdmin(conn.id, String(req.body.reason), userId);
      if (!ok) { jsonError(res, 404, 'not_found'); return; }
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.disable',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          reason: String(req.body.reason),
        },
        'success',
      );
      const updated = deps.connectionRepo.resolveConnection(conn.id)!;
      res.json({ connection: presentConnection(updated) });
    } catch (e) {
      logger.warn(`[ssh:api] admin disable failed err=${String(e)}`);
      jsonError(res, 500, 'disable_failed');
    }
  });

  router.patch('/connections/:id/enable', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      const ok = deps.connectionRepo.enableByAdmin(conn.id);
      if (!ok) { jsonError(res, 404, 'not_found'); return; }
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.enable',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          reason: String(req.body.reason),
        },
        'success',
      );
      const updated = deps.connectionRepo.resolveConnection(conn.id)!;
      res.json({ connection: presentConnection(updated) });
    } catch (e) {
      logger.warn(`[ssh:api] admin enable failed err=${String(e)}`);
      jsonError(res, 500, 'enable_failed');
    }
  });

  router.delete('/connections/:id', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      const auditId = deps.auditRepo.begin({
        action: 'ssh.connection.delete',
        entityType: 'ssh_connection',
        entityId: conn.id,
        connectionId: conn.id,
        ownerId: conn.ownerId,
        actingUserId: userId,
        reason: String(req.body.reason),
      });
      const ok = deps.connectionRepo.delete(conn.id);
      if (!ok) {
        deps.auditRepo.complete(auditId, 'failed', { err: 'no_changes' });
        jsonError(res, 404, 'not_found');
        return;
      }
      deps.auditRepo.complete(auditId, 'success');
      res.json({ ok: true });
    } catch (e) {
      logger.warn(`[ssh:api] admin delete failed err=${String(e)}`);
      jsonError(res, 500, 'delete_failed');
    }
  });

  router.post('/connections/:id/force-unlock', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const decision = deps.forceUnlockLimiter.check(userId);
      if (!decision.allowed) {
        if (decision.retryAfterSeconds !== undefined) {
          res.setHeader('Retry-After', String(decision.retryAfterSeconds));
        }
        jsonError(res, 429, 'rate_limited', { retryAfterSeconds: decision.retryAfterSeconds });
        return;
      }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      const removed = deps.abuseRepo.reset(`conn:${conn.id}`);
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.abuse.unlock_manual',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          reason: String(req.body.reason),
          detail: { removed },
        },
        'success',
      );
      res.json({ ok: true, removed });
    } catch (e) {
      logger.warn(`[ssh:api] admin force-unlock failed err=${String(e)}`);
      jsonError(res, 500, 'unlock_failed');
    }
  });

  // POST /api/ssh/admin/globals — create a global connection (owner_id=NULL).
  router.post('/globals', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    const userId = deps.getUserId(req);
    if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
    const reasonErr = validateReason(req.body?.reason);
    if (reasonErr) { jsonError(res, 400, reasonErr); return; }
    const body = req.body ?? {};
    const label = safeString(body.label, 200);
    const host = safeString(body.host, 255);
    const port = safePort(body.port);
    const username = safeString(body.username, 64);
    const denyPatterns = safeOptionalString(body.commandDenyPatterns, 4096);
    const allowPatterns = safeOptionalString(body.commandAllowPatterns, 4096);
    const allowRemoteUnrestricted = body.allowRemoteUnrestricted === true || body.allowRemoteUnrestricted === 1;
    const allowPrivateAddresses = body.allowPrivateAddresses === true || body.allowPrivateAddresses === 1;

    // remote_path_prefix is required UNLESS allow_remote_unrestricted is set.
    let remotePathPrefix: string | null;
    if (allowRemoteUnrestricted) {
      // Stored prefix is '/' (sandbox-effectively-disabled).
      remotePathPrefix = '/';
    } else {
      remotePathPrefix = safePathPrefix(body.remotePathPrefix);
    }

    // Keypair source: 'provided' (admin uploads PEM) or 'generate' (orchestrator
    // creates a fresh keypair; the public key is returned exactly once).
    const keypairSource = body.keypairSource === 'generate' ? 'generate' : 'provided';
    let pemBuf: Buffer;
    let passBuf: Buffer | null;
    if (keypairSource === 'generate') {
      const keyType = body.generateKeyType === 'rsa-4096' ? 'rsa-4096' : 'ed25519';
      const generated = deps.generateKeypair(keyType);
      pemBuf = generated.privateKeyPem;
      passBuf = null;
    } else {
      const privateKey = typeof body.privateKeyPem === 'string' ? body.privateKeyPem : null;
      const passphrase = typeof body.passphrase === 'string' ? body.passphrase : null;
      if (!privateKey) {
        jsonError(res, 400, 'invalid_input', {
          required: ['label', 'host', 'port (1-65535)', 'username', 'privateKeyPem', 'remotePathPrefix (or allowRemoteUnrestricted=true)'],
        });
        return;
      }
      pemBuf = Buffer.from(privateKey, 'utf8');
      passBuf = passphrase ? Buffer.from(passphrase, 'utf8') : null;
    }

    if (!label || !host || port === null || !username || !remotePathPrefix) {
      pemBuf.fill(0);
      if (passBuf) passBuf.fill(0);
      jsonError(res, 400, 'invalid_input', {
        required: ['label', 'host', 'port (1-65535)', 'username', 'privateKeyPem', 'remotePathPrefix (or allowRemoteUnrestricted=true)'],
      });
      return;
    }
    if (denyPatterns === null) {
      pemBuf.fill(0);
      if (passBuf) passBuf.fill(0);
      jsonError(res, 400, 'invalid_command_deny_patterns'); return;
    }
    if (allowPatterns === null) {
      pemBuf.fill(0);
      if (passBuf) passBuf.fill(0);
      jsonError(res, 400, 'invalid_command_allow_patterns'); return;
    }

    let encrypted: SshEncryptResult;
    try {
      encrypted = deps.encryptKeyMaterial(null, pemBuf, passBuf);
    } catch (e) {
      pemBuf.fill(0);
      if (passBuf) passBuf.fill(0);
      logger.warn(`[ssh:api] admin global encrypt failed err=${String(e)}`);
      jsonError(res, 500, 'encrypt_failed');
      return;
    }
    pemBuf.fill(0);
    if (passBuf) passBuf.fill(0);

    try {
      const conn = deps.connectionRepo.create({
        ownerId: null,
        label, host, port, username,
        privateKeyEnc: encrypted.blob,
        passphraseEnc: encrypted.passphraseBlob,
        keyVersion: encrypted.keyVersion,
        keyFingerprint: encrypted.fingerprint,
        remotePathPrefix,
        allowRemoteUnrestricted,
        allowPrivateAddresses,
        commandDenyPatterns: denyPatterns ?? null,
        commandAllowPatterns: allowPatterns ?? null,
      });
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.upsert',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: null,
          actingUserId: userId,
          reason: String(body.reason),
          detail: { op: 'create', scope: 'global', allowRemoteUnrestricted, allowPrivateAddresses, keypairSource },
        },
        'success',
      );
      res.status(201).json({
        connection: presentConnection(conn),
        publicKey: encrypted.publicKey,
      });
    } catch (e) {
      logger.warn(`[ssh:api] admin global create failed err=${String(e)}`);
      jsonError(res, 500, 'create_failed');
    }
  });

  router.patch('/globals/:id', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== null) { jsonError(res, 400, 'not_global'); return; }
      const body = req.body ?? {};
      const patch: Parameters<SshConnectionRepo['update']>[1] = {};
      if (body.label !== undefined) {
        const v = safeString(body.label, 200);
        if (v === null) { jsonError(res, 400, 'invalid_label'); return; }
        patch.label = v;
      }
      if (body.host !== undefined) {
        const v = safeString(body.host, 255);
        if (v === null) { jsonError(res, 400, 'invalid_host'); return; }
        patch.host = v;
      }
      if (body.port !== undefined) {
        const v = safePort(body.port);
        if (v === null) { jsonError(res, 400, 'invalid_port'); return; }
        patch.port = v;
      }
      if (body.username !== undefined) {
        const v = safeString(body.username, 64);
        if (v === null) { jsonError(res, 400, 'invalid_username'); return; }
        patch.username = v;
      }
      if (body.remotePathPrefix !== undefined) {
        const v = safePathPrefix(body.remotePathPrefix);
        if (v === null) { jsonError(res, 400, 'invalid_remote_path_prefix'); return; }
        patch.remotePathPrefix = v;
      }
      if (body.commandDenyPatterns !== undefined) {
        const v = safeOptionalString(body.commandDenyPatterns, 4096);
        if (v === null) { jsonError(res, 400, 'invalid_command_deny_patterns'); return; }
        patch.commandDenyPatterns = v ?? null;
      }
      if (body.commandAllowPatterns !== undefined) {
        const v = safeOptionalString(body.commandAllowPatterns, 4096);
        if (v === null) { jsonError(res, 400, 'invalid_command_allow_patterns'); return; }
        patch.commandAllowPatterns = v ?? null;
      }
      if (body.allowRemoteUnrestricted !== undefined) {
        patch.allowRemoteUnrestricted = body.allowRemoteUnrestricted === true || body.allowRemoteUnrestricted === 1;
      }
      if (body.allowPrivateAddresses !== undefined) {
        patch.allowPrivateAddresses = body.allowPrivateAddresses === true || body.allowPrivateAddresses === 1;
      }
      if (typeof body.privateKeyPem === 'string') {
        let pemBuf = Buffer.from(body.privateKeyPem, 'utf8');
        let passBuf = typeof body.passphrase === 'string' ? Buffer.from(body.passphrase, 'utf8') : null;
        try {
          const enc = deps.encryptKeyMaterial(null, pemBuf, passBuf);
          patch.privateKeyEnc = enc.blob;
          patch.passphraseEnc = enc.passphraseBlob;
          patch.keyVersion = enc.keyVersion;
          patch.keyFingerprint = enc.fingerprint;
        } finally {
          pemBuf.fill(0);
          if (passBuf) passBuf.fill(0);
        }
      }
      const ok = deps.connectionRepo.update(conn.id, patch);
      if (!ok) { jsonError(res, 404, 'not_found'); return; }
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.connection.upsert',
          entityType: 'ssh_connection',
          entityId: conn.id,
          connectionId: conn.id,
          ownerId: null,
          actingUserId: userId,
          reason: String(req.body.reason),
          detail: { op: 'update', scope: 'global', fields: Object.keys(patch) },
        },
        'success',
      );
      const updated = deps.connectionRepo.resolveConnection(conn.id)!;
      res.json({ connection: presentConnection(updated) });
    } catch (e) {
      logger.warn(`[ssh:api] admin global patch failed err=${String(e)}`);
      jsonError(res, 500, 'update_failed');
    }
  });

  router.delete('/globals/:id', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const conn = deps.connectionRepo.resolveConnection(req.params.id);
      if (!conn) { jsonError(res, 404, 'not_found'); return; }
      if (conn.ownerId !== null) { jsonError(res, 400, 'not_global'); return; }
      const auditId = deps.auditRepo.begin({
        action: 'ssh.connection.delete',
        entityType: 'ssh_connection',
        entityId: conn.id,
        connectionId: conn.id,
        ownerId: null,
        actingUserId: userId,
        reason: String(req.body.reason),
      });
      const ok = deps.connectionRepo.delete(conn.id);
      if (!ok) {
        deps.auditRepo.complete(auditId, 'failed', { err: 'no_changes' });
        jsonError(res, 404, 'not_found');
        return;
      }
      deps.auditRepo.complete(auditId, 'success');
      res.json({ ok: true });
    } catch (e) {
      logger.warn(`[ssh:api] admin global delete failed err=${String(e)}`);
      jsonError(res, 500, 'delete_failed');
    }
  });

  router.get('/grants', deps.requireAdmin, (req, res) => {
    try {
      const limit = safeLimit(req.query.limit, 100, 1000);
      const rows = deps.db.prepare(
        `SELECT * FROM ssh_connection_grants ORDER BY created_at DESC LIMIT ?`,
      ).all(limit) as Array<{
        id: string; connection_id: string; subject_type: SshGrantSubjectType; subject_id: string;
        piece_name: string | null; applies_to_all_pieces: number; granted_by_user_id: string;
        reason: string; expires_at: string | null; created_at: string;
      }>;
      const grants = rows.map((r) => ({
        id: r.id,
        connectionId: r.connection_id,
        subjectType: r.subject_type,
        subjectId: r.subject_id,
        pieceName: r.piece_name,
        appliesToAllPieces: r.applies_to_all_pieces === 1,
        grantedByUserId: r.granted_by_user_id,
        reason: r.reason,
        expiresAt: r.expires_at,
        createdAt: r.created_at,
      }));
      res.json({ grants });
    } catch (e) {
      logger.warn(`[ssh:api] admin list grants failed err=${String(e)}`);
      jsonError(res, 500, 'list_grants_failed');
    }
  });

  router.post('/grants', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const body = req.body ?? {};
      const reasonErr = validateReason(body.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const connectionId = safeUuid(body.connectionId);
      const subjectType = safeSubjectType(body.subjectType);
      const subjectId = safeString(body.subjectId, 128);
      const appliesToAllPieces = body.appliesToAllPieces === true || body.appliesToAllPieces === 1;
      const pieceName = safeOptionalString(body.pieceName, 64);
      const expiresAt = safeExpiresAt(body.expiresAt);
      if (!connectionId || !subjectType || !subjectId) {
        jsonError(res, 400, 'invalid_input', {
          required: ['connectionId', 'subjectType ("user"|"org")', 'subjectId', 'reason'],
        });
        return;
      }
      if (pieceName === null) { jsonError(res, 400, 'invalid_piece_name'); return; }
      if (expiresAt === null) { jsonError(res, 400, 'invalid_expires_at'); return; }
      if (!appliesToAllPieces && (!pieceName || pieceName.length === 0)) {
        jsonError(res, 400, 'piece_name_required_unless_applies_to_all'); return;
      }
      if (appliesToAllPieces && pieceName !== undefined && pieceName.length > 0) {
        jsonError(res, 400, 'piece_name_conflicts_with_applies_to_all'); return;
      }
      // Connection must exist before granting.
      const conn = deps.connectionRepo.resolveConnection(connectionId);
      if (!conn) { jsonError(res, 404, 'connection_not_found'); return; }
      let grant: SshGrant;
      try {
        grant = deps.grantsRepo.create({
          connectionId,
          subjectType,
          subjectId,
          pieceName: appliesToAllPieces ? null : (pieceName ?? null),
          appliesToAllPieces,
          grantedByUserId: userId,
          reason: String(body.reason),
          expiresAt,
        });
      } catch (e) {
        jsonError(res, 400, 'grant_create_failed', { detail: String(e) }); return;
      }
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.grant.create',
          entityType: 'ssh_grant',
          entityId: grant.id,
          connectionId: conn.id,
          ownerId: conn.ownerId,
          actingUserId: userId,
          reason: String(body.reason),
          detail: { subjectType, subjectId, pieceName: grant.pieceName, appliesToAllPieces },
        },
        'success',
      );
      res.status(201).json({ grant: presentGrant(grant) });
    } catch (e) {
      logger.warn(`[ssh:api] admin grant create failed err=${String(e)}`);
      jsonError(res, 500, 'grant_create_failed');
    }
  });

  router.delete('/grants/:id', deps.requireAdmin, (req, res) => {
    if (maintenance503(deps.maintenance, res)) return;
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      const grant = deps.grantsRepo.getById(req.params.id);
      if (!grant) { jsonError(res, 404, 'not_found'); return; }
      const ok = deps.grantsRepo.delete(grant.id);
      if (!ok) { jsonError(res, 404, 'not_found'); return; }
      // Kick any active console-WS viewers whose access depended on this grant.
      // user-subject grants map 1:1 to a single userId; org-subject grants are
      // deferred (next attach will be denied; existing viewers stay until the
      // session ends or the org-member's WS reconnects).
      let kicked: number | void = 0;
      if (grant.subjectType === 'user' && deps.onAccessRevoked) {
        kicked = deps.onAccessRevoked({ connectionId: grant.connectionId, userId: grant.subjectId });
      }
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.grant.delete',
          entityType: 'ssh_grant',
          entityId: grant.id,
          connectionId: grant.connectionId,
          actingUserId: userId,
          reason: String(req.body.reason),
          detail: {
            subjectType: grant.subjectType,
            subjectId: grant.subjectId,
            pieceName: grant.pieceName,
            viewersKicked: typeof kicked === 'number' ? kicked : 0,
          },
        },
        'success',
      );
      res.json({ ok: true });
    } catch (e) {
      logger.warn(`[ssh:api] admin grant delete failed err=${String(e)}`);
      jsonError(res, 500, 'grant_delete_failed');
    }
  });

  // POST /api/ssh/admin/rotate-master-key — stub for v1 (Phase 5).
  // Sets the maintenance flag and audits the intent. The actual DEK re-wrap
  // job is deferred to a follow-up PR per Phase 5 design note (line 728).
  // Returns 501 in the response body but 202 status so the UI can show a
  // banner; UI Phase 6 will key off the maintenance snapshot.
  router.post('/rotate-master-key', deps.requireAdmin, (req, res) => {
    try {
      const userId = deps.getUserId(req);
      if (!userId) { jsonError(res, 401, 'unauthorized'); return; }
      const reasonErr = validateReason(req.body?.reason);
      if (reasonErr) { jsonError(res, 400, reasonErr); return; }
      if (deps.maintenance.isActive()) {
        jsonError(res, 409, 'already_in_progress', { snapshot: deps.maintenance.snapshot() });
        return;
      }
      const jobId = `rotate-${Date.now().toString(36)}`;
      deps.maintenance.enter(String(req.body.reason), jobId);
      deps.auditRepo.beginAndComplete(
        {
          action: 'ssh.master_key.rotate.start',
          actingUserId: userId,
          reason: String(req.body.reason),
          detail: { jobId, status: 'stub', note: 'rotation job not implemented in v1; only maintenance flag set' },
        },
        'success',
      );
      res.status(202).json({
        jobId,
        status: 'maintenance_set',
        detail: 'maintenance flag is now active; actual DEK re-wrap is not implemented in v1',
        notImplemented: true,
      });
    } catch (e) {
      logger.warn(`[ssh:api] admin rotate-master-key failed err=${String(e)}`);
      jsonError(res, 500, 'rotate_failed');
    }
  });

  router.get('/rotate-master-key/:jobId', deps.requireAdmin, (req, res) => {
    try {
      const snap = deps.maintenance.snapshot();
      if (!snap.active || snap.jobId !== req.params.jobId) {
        jsonError(res, 404, 'not_found_or_completed', { snapshot: snap });
        return;
      }
      res.json({
        jobId: snap.jobId,
        status: 'in_progress',
        startedAt: snap.enteredAt,
        progress: { note: 'v1 stub: no rewrap performed' },
        notImplemented: true,
      });
    } catch (e) {
      logger.warn(`[ssh:api] admin rotate-master-key get failed err=${String(e)}`);
      jsonError(res, 500, 'rotate_get_failed');
    }
  });

  // GET /api/ssh/admin/audit — cross-user audit query.
  router.get('/audit', deps.requireAdmin, (req, res) => {
    try {
      const limit = safeLimit(req.query.limit, 100, 1000);
      const action = typeof req.query.action === 'string' ? req.query.action : null;
      const ownerId = typeof req.query.ownerId === 'string' ? req.query.ownerId : null;
      const connectionId = typeof req.query.connectionId === 'string' ? req.query.connectionId : null;
      const outcome = typeof req.query.outcome === 'string' ? req.query.outcome : null;

      const where: string[] = [];
      const params: unknown[] = [];
      if (action) { where.push('action = ?'); params.push(action); }
      if (ownerId) { where.push('owner_id = ?'); params.push(ownerId); }
      if (connectionId) { where.push('connection_id = ?'); params.push(connectionId); }
      if (outcome) { where.push('outcome = ?'); params.push(outcome); }
      const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
      const sql = `SELECT * FROM ssh_audit_log ${whereClause} ORDER BY started_at DESC LIMIT ?`;
      params.push(limit);
      const rows = deps.db.prepare(sql).all(...params) as Array<{
        id: number; action: string; entity_type: string | null; entity_id: string | null;
        connection_id: string | null; owner_id: string | null; acting_user_id: string | null;
        job_id: string | null; piece_name: string | null; outcome: string;
        reason: string | null; detail: string | null; started_at: string; completed_at: string | null;
      }>;
      const audit = rows.map((r) => ({
        id: r.id,
        action: r.action,
        entityType: r.entity_type,
        entityId: r.entity_id,
        connectionId: r.connection_id,
        ownerId: r.owner_id,
        actingUserId: r.acting_user_id,
        jobId: r.job_id,
        pieceName: r.piece_name,
        outcome: r.outcome,
        reason: r.reason,
        detail: r.detail ? JSON.parse(r.detail) : null,
        startedAt: r.started_at,
        completedAt: r.completed_at,
      }));
      res.json({ audit });
    } catch (e) {
      logger.warn(`[ssh:api] admin audit list failed err=${String(e)}`);
      jsonError(res, 500, 'audit_failed');
    }
  });

  return router;
}
