import { logError } from "@/lib/logger";
import { error as errorResponse, success } from "@/lib/response";
import { ConsoleSettingsRepository } from "@/internal/repository/admin_console/console_settings";
import type { Context } from "hono";

/**
 * Console-wide settings.
 *
 * Reads are open to any authenticated console user — every user needs the theme
 * in order to render it. Writes are super-admin only: this is a shared setting,
 * so a normal operator saving one would restyle the console for everybody.
 */

/** Settings the console is allowed to read/write through this endpoint. */
const ALLOWED_KEYS = new Set([
  "promo-appearance",
  /*
   * Which columns the Viewpoint bookings table shows.
   *
   * Console-wide for the same reason the theme is: a super admin decides what the
   * table looks like and every user sees that, rather than each browser keeping its
   * own idea of it. The imported reports vary in which fields they carry, so the
   * useful column set is a judgement about the data currently loaded — not a
   * per-person preference.
   */
  "viewpoint-booking-columns",
  /*
   * Account deletion: `{ followupDays: number }`.
   *
   * Console-wide rather than per-deletion because it is a policy about what members
   * are told, not an operator's choice. Each deletion still stores the window it
   * promised, so changing this never moves mail already committed at the old one.
   */
  "account-deletion",
]);

/**
 * Bound on a stored setting, in bytes of serialised JSON.
 *
 * The promo theme is six short strings (~150 bytes). The limit exists so a
 * malformed or hostile client can't park an arbitrarily large blob in a row that
 * every console page then loads on every request.
 */
const MAX_VALUE_BYTES = 8 * 1024;

function assertAllowedKey(c: Context): string | null {
  const key = c.req.param("key");
  if (!key || !ALLOWED_KEYS.has(key)) return null;
  return key;
}

export const getConsoleSettingHandler = async (c: Context) => {
  try {
    const key = assertAllowedKey(c);
    if (!key) return errorResponse(c, "Unknown setting", 404);

    const repo = new ConsoleSettingsRepository(c.get("datastore"));
    const row = await repo.get(key);

    // A setting that has never been saved is not an error — the client falls
    // back to its own defaults, so null is a valid answer.
    return success(
      c,
      row
        ? {
            key: row.key,
            value: row.value,
            updatedAt: row.updated_at,
            updatedBy: row.updated_by,
          }
        : { key, value: null, updatedAt: null, updatedBy: null },
      "Setting fetched",
      200,
    );
  } catch (err: any) {
    logError("Error in getConsoleSettingHandler:", err);
    return errorResponse(c, err?.message || "Failed to fetch setting", 500);
  }
};

export const putConsoleSettingHandler = async (c: Context) => {
  try {
    const key = assertAllowedKey(c);
    if (!key) return errorResponse(c, "Unknown setting", 404);

    const isSuperAdmin = c.get("isAdminConsoleSuperAdmin") === true;
    if (!isSuperAdmin) {
      return errorResponse(
        c,
        "Only a super admin can change console-wide settings.",
        403,
      );
    }

    const body = await c.req.json().catch(() => null);
    if (!body || typeof body !== "object" || !("value" in body)) {
      return errorResponse(c, "Expected a JSON body with a `value`.", 400);
    }

    const value = (body as { value: unknown }).value;
    if (value === null || typeof value !== "object") {
      return errorResponse(c, "`value` must be an object.", 400);
    }

    const size = Buffer.byteLength(JSON.stringify(value), "utf8");
    if (size > MAX_VALUE_BYTES) {
      return errorResponse(
        c,
        `Setting is too large (${size} bytes, limit ${MAX_VALUE_BYTES}).`,
        413,
      );
    }

    const repo = new ConsoleSettingsRepository(c.get("datastore"));
    const updatedBy = String(c.get("adminEmail") ?? c.get("consoleUserId") ?? "");
    const row = await repo.put(key, value, updatedBy || null);

    return success(
      c,
      {
        key: row.key,
        value: row.value,
        updatedAt: row.updated_at,
        updatedBy: row.updated_by,
      },
      "Setting saved",
      200,
    );
  } catch (err: any) {
    logError("Error in putConsoleSettingHandler:", err);
    return errorResponse(c, err?.message || "Failed to save setting", 500);
  }
};
