import type { AuthenticatedRequest } from "../../../types/index.js";
import type { RequestHandler, Router } from "express";
import { restartGuacServer } from "../../hosts/guacamole/guacamole-server.js";
import {
  authLogger,
  getGlobalLogLevel,
  setGlobalLogLevel,
} from "../../utils/logger.js";
import { logAudit, getRequestMeta } from "../../utils/audit-logger.js";
import { getTelemetryEnvOverride } from "../../utils/analytics.js";
import { AI_PRIVATE_ALLOWLIST_KEY, parseAllowlist } from "../../ai/egress.js";
import {
  createCurrentSettingsRepository,
  createCurrentUserRepository,
} from "../repositories/factory.js";
import type { UserRecord } from "../repositories/user-repository.js";

function getDefaultGuacUrl(): string {
  return `${process.env.GUACD_HOST || "localhost"}:${process.env.GUACD_PORT || "4822"}`;
}

export type HostDefaults = {
  useSocks5?: boolean;
  socks5Host?: string;
  socks5Port?: number;
  socks5Username?: string;
  socks5Password?: string;
  credentialId?: number | null;
  metricsEnabled?: boolean;
  statusCheckEnabled?: boolean;
  fontSize?: number;
  fontFamily?: string;
  theme?: string;
  cursorStyle?: string;
  cursorBlink?: boolean;
  enableSessionLogging?: boolean;
  enableCommandHistory?: boolean;
};

async function getAdminActor(
  userId: string | undefined,
): Promise<UserRecord | null> {
  if (!userId) return null;
  const user = await createCurrentUserRepository().findById(userId);
  return user?.isAdmin ? user : null;
}

export function registerUserSettingsRoutes(
  router: Router,
  authenticateJWT: RequestHandler,
): void {
  /**
   * @openapi
   * /users/guacamole-settings:
   *   get:
   *     summary: Get Guacamole settings
   *     description: Returns current guacd enabled status and host:port URL. No authentication required.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Guacamole settings.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 enabled:
   *                   type: boolean
   *                 url:
   *                   type: string
   *       500:
   *         description: Failed to get guacamole settings.
   */
  router.get("/guacamole-settings", authenticateJWT, async (_req, res) => {
    try {
      const settings = createCurrentSettingsRepository();
      const enabled = await settings.getBoolean("guac_enabled", true);
      const url = await settings.get("guac_url");
      res.json({
        enabled,
        url: url ?? getDefaultGuacUrl(),
      });
    } catch (err) {
      authLogger.error("Failed to get guacamole settings", err);
      res.status(500).json({ error: "Failed to get guacamole settings" });
    }
  });

  /**
   * @openapi
   * /users/guacamole-settings:
   *   patch:
   *     summary: Update Guacamole settings
   *     description: Admin-only. Updates guacd enabled status and/or host:port URL.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               enabled:
   *                 type: boolean
   *               url:
   *                 type: string
   *     responses:
   *       200:
   *         description: Guacamole settings updated.
   *       403:
   *         description: Not authorized.
   *       500:
   *         description: Failed to update guacamole settings.
   */
  router.patch("/guacamole-settings", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const { enabled, url } = req.body;
      const settings = createCurrentSettingsRepository();
      if (typeof enabled === "boolean") {
        await settings.set("guac_enabled", enabled ? "true" : "false");
      }
      if (typeof url === "string") {
        await settings.set("guac_url", url);
        try {
          await restartGuacServer();
        } catch (err) {
          authLogger.error(
            "Failed to restart guac server after URL update",
            err,
          );
        }
      }
      const currentEnabled = await settings.getBoolean("guac_enabled", true);
      const currentUrl = await settings.get("guac_url");

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_guacamole_settings",
        resourceType: "setting",
        details: JSON.stringify({ enabled, url }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({
        enabled: currentEnabled,
        url: currentUrl ?? getDefaultGuacUrl(),
      });
    } catch (err) {
      authLogger.error("Failed to update guacamole settings", err);
      res.status(500).json({ error: "Failed to update guacamole settings" });
    }
  });

  /**
   * @openapi
   * /users/log-level:
   *   get:
   *     summary: Get log level setting
   *     description: Returns the configured log verbosity level.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Current log level.
   */
  router.get("/log-level", authenticateJWT, async (_req, res) => {
    try {
      const level = await createCurrentSettingsRepository().get("log_level");
      res.json({
        level: level ?? getGlobalLogLevel(),
      });
    } catch (err) {
      authLogger.error("Failed to get log level", err);
      res.status(500).json({ error: "Failed to get log level" });
    }
  });

  /**
   * @openapi
   * /users/log-level:
   *   patch:
   *     summary: Update log level setting (admin only)
   *     description: Sets the log verbosity level.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Log level updated.
   *       400:
   *         description: Invalid log level.
   *       403:
   *         description: Not authorized.
   */
  router.patch("/log-level", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const { level } = req.body;
      const validLevels = ["debug", "info", "warn", "error"];
      if (typeof level !== "string" || !validLevels.includes(level)) {
        return res
          .status(400)
          .json({ error: "level must be one of: debug, info, warn, error" });
      }
      await createCurrentSettingsRepository().set("log_level", level);
      setGlobalLogLevel(level);

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_log_level",
        resourceType: "setting",
        details: JSON.stringify({ level }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ level });
    } catch (err) {
      authLogger.error("Failed to set log level", err);
      res.status(500).json({ error: "Failed to set log level" });
    }
  });

  /**
   * @openapi
   * /users/session-timeout:
   *   get:
   *     summary: Get session timeout setting
   *     description: Returns the configured session timeout in hours.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Current session timeout hours.
   */
  router.get("/session-timeout", authenticateJWT, async (_req, res) => {
    try {
      const value = await createCurrentSettingsRepository().get(
        "session_timeout_hours",
      );
      res.json({
        timeoutHours: value ? parseInt(value, 10) : 24,
      });
    } catch (err) {
      authLogger.error("Failed to get session timeout", err);
      res.status(500).json({ error: "Failed to get session timeout" });
    }
  });

  /**
   * @openapi
   * /users/session-timeout:
   *   patch:
   *     summary: Update session timeout setting (admin only)
   *     description: Sets the session timeout in hours.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Session timeout updated.
   *       400:
   *         description: Invalid value.
   *       403:
   *         description: Not authorized.
   */
  router.patch("/session-timeout", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const { timeoutHours } = req.body;
      if (
        typeof timeoutHours !== "number" ||
        timeoutHours < 1 ||
        timeoutHours > 720
      ) {
        return res
          .status(400)
          .json({ error: "timeoutHours must be between 1 and 720" });
      }
      await createCurrentSettingsRepository().set(
        "session_timeout_hours",
        String(timeoutHours),
      );

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_session_timeout",
        resourceType: "setting",
        details: JSON.stringify({ timeoutHours }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ timeoutHours });
    } catch (err) {
      authLogger.error("Failed to set session timeout", err);
      res.status(500).json({ error: "Failed to set session timeout" });
    }
  });

  /**
   * @openapi
   * /users/tailscale-settings:
   *   get:
   *     summary: Get Tailscale settings
   *     description: Returns whether a Tailscale API key is configured (value is masked).
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Tailscale settings.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 apiKey:
   *                   type: string
   *                   description: Masked API key or empty string if not set.
   *                 hasApiKey:
   *                   type: boolean
   *                 apiBaseUrl:
   *                   type: string
   *                   description: Custom control-plane API base URL (e.g. a Headscale instance), or empty string for the Tailscale default.
   */
  router.get("/tailscale-settings", authenticateJWT, async (_req, res) => {
    try {
      const settingsRepo = createCurrentSettingsRepository();
      const apiKey = (await settingsRepo.get("tailscale_api_key")) ?? "";
      const apiBaseUrl =
        (await settingsRepo.get("tailscale_api_base_url")) ?? "";
      res.json({
        apiKey: apiKey
          ? `${apiKey.slice(0, 6)}${"*".repeat(Math.max(0, apiKey.length - 6))}`
          : "",
        hasApiKey: !!apiKey,
        apiBaseUrl,
      });
    } catch (err) {
      authLogger.error("Failed to get Tailscale settings", err);
      res.status(500).json({ error: "Failed to get Tailscale settings" });
    }
  });

  /**
   * @openapi
   * /users/tailscale-settings:
   *   patch:
   *     summary: Update Tailscale settings (admin only)
   *     description: Saves or clears the Tailscale API key used for device discovery.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               apiKey:
   *                 type: string
   *               apiBaseUrl:
   *                 type: string
   *                 description: Optional custom control-plane API base URL (e.g. a Headscale instance). Leave empty to use the Tailscale default.
   *     responses:
   *       200:
   *         description: Tailscale settings updated.
   *       403:
   *         description: Not authorized.
   *       500:
   *         description: Failed to update Tailscale settings.
   */
  router.patch("/tailscale-settings", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const { apiKey, apiBaseUrl } = req.body;
      if (typeof apiKey !== "string") {
        return res.status(400).json({ error: "apiKey must be a string" });
      }
      if (apiBaseUrl !== undefined && typeof apiBaseUrl !== "string") {
        return res.status(400).json({ error: "apiBaseUrl must be a string" });
      }
      const settingsRepo = createCurrentSettingsRepository();
      await settingsRepo.set("tailscale_api_key", apiKey);
      if (apiBaseUrl !== undefined) {
        await settingsRepo.set(
          "tailscale_api_base_url",
          apiBaseUrl.trim().replace(/\/+$/, ""),
        );
      }

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_tailscale_settings",
        resourceType: "setting",
        details: JSON.stringify({ hasApiKey: !!apiKey }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ hasApiKey: !!apiKey });
    } catch (err) {
      authLogger.error("Failed to update Tailscale settings", err);
      res.status(500).json({ error: "Failed to update Tailscale settings" });
    }
  });

  /**
   * @openapi
   * /users/command-history-enabled:
   *   get:
   *     summary: Get command history enabled setting
   *     description: Returns whether command history recording is globally enabled.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Command history enabled status.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 enabled:
   *                   type: boolean
   */
  router.get("/command-history-enabled", authenticateJWT, async (_req, res) => {
    try {
      res.json({
        enabled: await createCurrentSettingsRepository().getBoolean(
          "command_history_enabled",
          true,
        ),
      });
    } catch (err) {
      authLogger.error("Failed to get command history enabled setting", err);
      res
        .status(500)
        .json({ error: "Failed to get command history enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/command-history-enabled:
   *   patch:
   *     summary: Update command history enabled setting (admin only)
   *     description: Globally enables or disables command history recording for all hosts.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               enabled:
   *                 type: boolean
   *     responses:
   *       200:
   *         description: Setting updated.
   *       403:
   *         description: Not authorized.
   *       500:
   *         description: Failed to update setting.
   */
  router.patch(
    "/command-history-enabled",
    authenticateJWT,
    async (req, res) => {
      const userId = (req as AuthenticatedRequest).userId;
      try {
        const actor = await getAdminActor(userId);
        if (!actor) {
          return res.status(403).json({ error: "Not authorized" });
        }
        const { enabled } = req.body;
        if (typeof enabled !== "boolean") {
          return res.status(400).json({ error: "enabled must be a boolean" });
        }
        await createCurrentSettingsRepository().set(
          "command_history_enabled",
          enabled ? "true" : "false",
        );

        const { ipAddress, userAgent } = getRequestMeta(req);
        await logAudit({
          userId,
          username: actor.username ?? userId,
          action: "update_command_history_enabled",
          resourceType: "setting",
          details: JSON.stringify({ enabled }),
          ipAddress,
          userAgent,
          success: true,
        });

        res.json({ enabled });
      } catch (err) {
        authLogger.error(
          "Failed to update command history enabled setting",
          err,
        );
        res
          .status(500)
          .json({ error: "Failed to update command history enabled setting" });
      }
    },
  );

  /**
   * @openapi
   * /users/analytics-enabled:
   *   get:
   *     summary: Get analytics enabled setting
   *     description: Returns whether anonymous usage telemetry is enabled, and whether the value is locked by the ENABLE_TELEMETRY environment variable.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Analytics enabled status.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 enabled:
   *                   type: boolean
   *                 locked:
   *                   type: boolean
   */
  router.get("/analytics-enabled", authenticateJWT, async (_req, res) => {
    try {
      const override = getTelemetryEnvOverride();
      if (override !== null) {
        return res.json({ enabled: override, locked: true });
      }
      res.json({
        enabled: await createCurrentSettingsRepository().getBoolean(
          "analytics_enabled",
          true,
        ),
        locked: false,
      });
    } catch (err) {
      authLogger.error("Failed to get analytics enabled setting", err);
      res
        .status(500)
        .json({ error: "Failed to get analytics enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/analytics-enabled:
   *   patch:
   *     summary: Update analytics enabled setting (admin only)
   *     description: Enables or disables the daily anonymous usage telemetry heartbeat.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               enabled:
   *                 type: boolean
   *     responses:
   *       200:
   *         description: Setting updated.
   *       403:
   *         description: Not authorized.
   *       409:
   *         description: Setting is locked by the ENABLE_TELEMETRY environment variable.
   *       500:
   *         description: Failed to update setting.
   */
  router.patch("/analytics-enabled", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      if (getTelemetryEnvOverride() !== null) {
        return res.status(409).json({
          error: "Telemetry is locked by the ENABLE_TELEMETRY env variable",
        });
      }
      const { enabled } = req.body;
      if (typeof enabled !== "boolean") {
        return res.status(400).json({ error: "enabled must be a boolean" });
      }
      await createCurrentSettingsRepository().set(
        "analytics_enabled",
        enabled ? "true" : "false",
      );

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_analytics_enabled",
        resourceType: "setting",
        details: JSON.stringify({ enabled }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ enabled });
    } catch (err) {
      authLogger.error("Failed to update analytics enabled setting", err);
      res
        .status(500)
        .json({ error: "Failed to update analytics enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/session-sharing-enabled:
   *   get:
   *     summary: Get session sharing globally enabled setting
   *     description: Returns whether live session sharing (terminal/RDP/VNC/Telnet share links and in-app joins) is allowed instance-wide. Overrides every per-host toggle when false.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Session sharing enabled status.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 enabled:
   *                   type: boolean
   */
  router.get("/session-sharing-enabled", authenticateJWT, async (_req, res) => {
    try {
      res.json({
        enabled: await createCurrentSettingsRepository().getBoolean(
          "session_sharing_globally_enabled",
          true,
        ),
      });
    } catch (err) {
      authLogger.error("Failed to get session sharing enabled setting", err);
      res
        .status(500)
        .json({ error: "Failed to get session sharing enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/session-sharing-enabled:
   *   patch:
   *     summary: Update session sharing globally enabled setting (admin only)
   *     description: Enables or disables live session sharing instance-wide, overriding every per-host allowSessionSharing toggle.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               enabled:
   *                 type: boolean
   *     responses:
   *       200:
   *         description: Setting updated.
   *       403:
   *         description: Not authorized.
   *       500:
   *         description: Failed to update setting.
   */
  router.patch(
    "/session-sharing-enabled",
    authenticateJWT,
    async (req, res) => {
      const userId = (req as AuthenticatedRequest).userId;
      try {
        const actor = await getAdminActor(userId);
        if (!actor) {
          return res.status(403).json({ error: "Not authorized" });
        }
        const { enabled } = req.body;
        if (typeof enabled !== "boolean") {
          return res.status(400).json({ error: "enabled must be a boolean" });
        }
        await createCurrentSettingsRepository().set(
          "session_sharing_globally_enabled",
          enabled ? "true" : "false",
        );

        const { ipAddress, userAgent } = getRequestMeta(req);
        await logAudit({
          userId,
          username: actor.username ?? userId,
          action: "update_session_sharing_enabled",
          resourceType: "setting",
          details: JSON.stringify({ enabled }),
          ipAddress,
          userAgent,
          success: true,
        });

        res.json({ enabled });
      } catch (err) {
        authLogger.error(
          "Failed to update session sharing enabled setting",
          err,
        );
        res
          .status(500)
          .json({ error: "Failed to update session sharing enabled setting" });
      }
    },
  );

  /**
   * @openapi
   * /users/ai-enabled:
   *   get:
   *     summary: Get whether the AI assistant is enabled instance-wide
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: AI enabled status.
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 enabled:
   *                   type: boolean
   */
  router.get("/ai-enabled", authenticateJWT, async (_req, res) => {
    try {
      res.json({
        // Defaults to false so upgrading an install never turns the assistant
        // on without an admin deciding to.
        enabled: await createCurrentSettingsRepository().getBoolean(
          "ai_globally_enabled",
          false,
        ),
      });
    } catch (err) {
      authLogger.error("Failed to get AI enabled setting", err);
      res.status(500).json({ error: "Failed to get AI enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/ai-enabled:
   *   patch:
   *     summary: Update the instance-wide AI assistant setting (admin only)
   *     description: Turning this off hides and blocks the assistant for every user, whatever their own preference says.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               enabled:
   *                 type: boolean
   *     responses:
   *       200:
   *         description: Setting updated.
   *       403:
   *         description: Not authorized.
   */
  router.patch("/ai-enabled", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const { enabled } = req.body;
      if (typeof enabled !== "boolean") {
        return res.status(400).json({ error: "enabled must be a boolean" });
      }
      await createCurrentSettingsRepository().set(
        "ai_globally_enabled",
        enabled ? "true" : "false",
      );

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_ai_enabled",
        resourceType: "setting",
        details: JSON.stringify({ enabled }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ enabled });
    } catch (err) {
      authLogger.error("Failed to update AI enabled setting", err);
      res.status(500).json({ error: "Failed to update AI enabled setting" });
    }
  });

  /**
   * @openapi
   * /users/ai-private-endpoints:
   *   get:
   *     summary: Get the allowlist of private AI endpoint hosts
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Allowed hosts.
   */
  router.get("/ai-private-endpoints", authenticateJWT, async (_req, res) => {
    try {
      const raw = await createCurrentSettingsRepository().get(
        AI_PRIVATE_ALLOWLIST_KEY,
      );
      res.json({ hosts: parseAllowlist(raw) });
    } catch (err) {
      authLogger.error("Failed to get AI private endpoint allowlist", err);
      res.status(500).json({ error: "Failed to get the allowlist" });
    }
  });

  /**
   * @openapi
   * /users/ai-private-endpoints:
   *   patch:
   *     summary: Replace the allowlist of private AI endpoint hosts (admin only)
   *     description: >
   *       Providers on private or loopback addresses, such as a self-hosted
   *       Ollama, are refused unless their host appears here. Without this an
   *       ordinary user could point a provider at an internal service and use
   *       the server as a probe of its own network.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             properties:
   *               hosts:
   *                 type: array
   *                 items:
   *                   type: string
   *     responses:
   *       200:
   *         description: Allowlist updated.
   *       403:
   *         description: Not authorized.
   */
  router.patch("/ai-private-endpoints", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }

      const { hosts } = req.body;
      if (!Array.isArray(hosts)) {
        return res.status(400).json({ error: "hosts must be an array" });
      }
      if (hosts.length > 50) {
        return res.status(400).json({ error: "At most 50 hosts are allowed" });
      }

      const cleaned: string[] = [];
      for (const entry of hosts) {
        if (typeof entry !== "string") {
          return res.status(400).json({ error: "Each host must be a string" });
        }
        const host = entry.trim().toLowerCase();
        if (!host) continue;
        // A bare host, not a URL: no scheme, path, port or whitespace.
        if (!/^[a-z0-9._:-]+$/.test(host)) {
          return res
            .status(400)
            .json({ error: `${entry} is not a valid hostname` });
        }
        if (!cleaned.includes(host)) cleaned.push(host);
      }

      await createCurrentSettingsRepository().set(
        AI_PRIVATE_ALLOWLIST_KEY,
        JSON.stringify(cleaned),
      );

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_ai_private_endpoints",
        resourceType: "setting",
        details: JSON.stringify({ hosts: cleaned }),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json({ hosts: cleaned });
    } catch (err) {
      authLogger.error("Failed to update AI private endpoint allowlist", err);
      res.status(500).json({ error: "Failed to update the allowlist" });
    }
  });

  /**
   * @openapi
   * /users/host-defaults:
   *   get:
   *     summary: Get host creation defaults
   *     description: Returns the global default settings applied when creating a new host.
   *     tags:
   *       - Users
   *     responses:
   *       200:
   *         description: Host defaults object.
   *       500:
   *         description: Failed to get host defaults.
   */
  router.get("/host-defaults", authenticateJWT, async (_req, res) => {
    try {
      const value =
        await createCurrentSettingsRepository().get("host_defaults");
      const defaults: HostDefaults = value ? JSON.parse(value) : {};
      res.json(defaults);
    } catch (err) {
      authLogger.error("Failed to get host defaults", err);
      res.status(500).json({ error: "Failed to get host defaults" });
    }
  });

  /**
   * @openapi
   * /users/host-defaults:
   *   patch:
   *     summary: Update host creation defaults (admin only)
   *     description: Sets global default settings applied when a new host is created.
   *     tags:
   *       - Users
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *     responses:
   *       200:
   *         description: Host defaults updated.
   *       403:
   *         description: Not authorized.
   *       500:
   *         description: Failed to update host defaults.
   */
  router.patch("/host-defaults", authenticateJWT, async (req, res) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const actor = await getAdminActor(userId);
      if (!actor) {
        return res.status(403).json({ error: "Not authorized" });
      }
      const defaults: HostDefaults = req.body;
      await createCurrentSettingsRepository().set(
        "host_defaults",
        JSON.stringify(defaults),
      );

      const { ipAddress, userAgent } = getRequestMeta(req);
      await logAudit({
        userId,
        username: actor.username ?? userId,
        action: "update_host_defaults",
        resourceType: "setting",
        details: JSON.stringify(defaults),
        ipAddress,
        userAgent,
        success: true,
      });

      res.json(defaults);
    } catch (err) {
      authLogger.error("Failed to update host defaults", err);
      res.status(500).json({ error: "Failed to update host defaults" });
    }
  });
}
