import { defineMiddleware } from 'astro:middleware';
import { enforceAdminRateLimit, type AdminLimiterEnv } from './lib/rate-limit.js';
import { getFullDb } from './lib/db.js';
import { getSetting, SETTINGS_KEYS, parseMaintenance } from './lib/settings.js';
import { runBootGate } from './lib/boot-gate.js';
import { runMaintenanceGate } from './lib/maintenance.js';
import { resolveSession } from './lib/auth.js';
import { buildAuthEngine, buildServiceTokenResolver, type AdminEnv } from './lib/admin-engine.js';
import { env } from 'cloudflare:workers';

export const onRequest = defineMiddleware(async (context, next) => {
  const pathname = context.url.pathname;
  // Secret-gated infra route; must reach its handler regardless of install/maintenance state (auth: x-mod-cron-secret, re-checked in route + DO).
  if (pathname === '/cron-arm') return next();

  const cfEnv = env as (AdminLimiterEnv & AdminEnv & { DATABASE_URL?: string }) | undefined;

  // 1. Admin edge rate-limit (existing) — unchanged.
  if (cfEnv) {
    const limited = await enforceAdminRateLimit(cfEnv, context.request);
    if (limited) return limited;
  }

  // 2. Boot-mode gate — unconfigured sites route to /install (UX router; fail-open on read error).
  if (cfEnv?.DATABASE_URL) {
    const bootBlocked = await runBootGate(
      { url: context.url },
      {
        loadSiteConfigured: () => getSetting(getFullDb(cfEnv).db, SETTINGS_KEYS.siteConfigured),
      },
    );
    if (bootBlocked) return bootBlocked;
  }

  // 3. Maintenance gate. loadConfig/resolveRoles are thunks — only invoked on non-exempt/enabled
  //    paths (see runMaintenanceGate). Both NEVER throw out of the gate, but they fail in OPPOSITE
  //    directions, by design:
  //    - loadConfig fails OPEN: a transient DB read error → `{ enabled: false }` → site stays UP.
  //      Maintenance is an availability tool, not a security boundary; an inability to read the flag
  //      must NOT 500 every public request (this thunk runs on every non-exempt page view). Mirrors
  //      maintenance.astro, which try/catches the same read for the same reason.
  //    - resolveRoles fails CLOSED: any failure (anonymous, invalid/expired session, rate-limited
  //      refresh) → null → 503 (safe; an admin still resolves normally and is admitted by the
  //      operator floor).
  if (cfEnv?.DATABASE_URL) {
    const blocked = await runMaintenanceGate(
      { url: context.url, rewrite: (path) => context.rewrite(path) },
      {
        loadConfig: async () => {
          try {
            return parseMaintenance(await getSetting(getFullDb(cfEnv).db, SETTINGS_KEYS.maintenance));
          } catch {
            return { enabled: false };
          }
        },
        resolveRoles: async () => {
          try {
            const { principal } = await resolveSession(
              context.request,
              buildAuthEngine(cfEnv),
              context.cookies,
              buildServiceTokenResolver(cfEnv),
            );
            return principal.roles ?? [];
          } catch {
            return null;
          }
        },
      },
    );
    if (blocked) return blocked;
  }

  return next();
});
