// src/lib/auth/sys-permissions.ts — plan SYSTÈME : permissions atomiques.
// Généralise super-admin.ts (spec 2026-07-09) :
//   Admin SYS (env SUPER_ADMIN_EMAILS) → toutes les permissions, implicitement.
//   Staff SYS (user.role = "staff")    → sous-ensemble en DB (staff_permission).
// Ce gate est le SEUL chemin d'autorisation du back-office : adminRoles Better
// Auth reste ["admin"], un Staff SYS ne peut pas frapper /api/auth/admin/* en
// direct (spec §5/§8.6).
import "server-only";
import { eq } from "drizzle-orm";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { staffPermission } from "@/lib/db/admin-schema";
import { db } from "@/lib/db/client";
import { isSuperAdmin } from "./super-admin";
// Constantes pures (groupes/types de permissions) : définies dans
// sys-permissions-const.ts, SANS `server-only`, pour rester importables côté
// client (grille de permissions /admin/staff). Réexportées ci-dessous : aucun
// changement d'API pour les consommateurs serveur existants.
import {
  GRANTABLE_SYS_PERMISSIONS,
  type GrantableSysPermission,
  isGrantableSysPermission,
  RESERVED_SYS_PERMISSIONS,
  SYS_PERMISSION_GROUPS,
  type SysPermission,
} from "./sys-permissions-const";

export {
  GRANTABLE_SYS_PERMISSIONS,
  type GrantableSysPermission,
  isGrantableSysPermission,
  RESERVED_SYS_PERMISSIONS,
  SYS_PERMISSION_GROUPS,
  type SysPermission,
};

export interface SysOperatorSession {
  userId: string;
  email: string;
  kind: "admin-sys" | "staff-sys";
  twoFactorEnabled: boolean;
}

/** Résolution PURE du gate (testable sans DB/session). Un staff ne passe
 *  JAMAIS une permission réservée, même si une ligne fantôme existait en DB. */
export function resolveSysPermission(
  op: SysOperatorSession | null,
  perm: SysPermission,
  staffGrants: ReadonlySet<string>,
): "ok" | "forbidden" | "2fa-required" {
  if (!op) return "forbidden";
  if (!op.twoFactorEnabled) return "2fa-required";
  if (op.kind === "admin-sys") return "ok";
  if ((RESERVED_SYS_PERMISSIONS as readonly string[]).includes(perm)) {
    return "forbidden";
  }
  return staffGrants.has(perm) ? "ok" : "forbidden";
}

/** Session fraîche (jamais le cookie cache 5 min — même règle que god-mode) →
 *  l'opérateur SYS courant, ou null. */
export async function getSysOperatorSession(): Promise<SysOperatorSession | null> {
  const session = await auth.api.getSession({
    headers: await headers(),
    query: { disableCookieCache: true },
  });
  const u = session?.user;
  if (!u) return null;
  // `disableCookieCache` above forces a fresh user fetch → twoFactorEnabled is
  // up to date right after enrollment (no stale cookie-cache read).
  const twoFactorEnabled = Boolean(
    (u as { twoFactorEnabled?: boolean }).twoFactorEnabled,
  );
  if (isSuperAdmin(u.email)) {
    return {
      userId: u.id,
      email: u.email,
      kind: "admin-sys",
      twoFactorEnabled,
    };
  }
  if ((u as { role?: string | null }).role === "staff") {
    return {
      userId: u.id,
      email: u.email,
      kind: "staff-sys",
      twoFactorEnabled,
    };
  }
  return null;
}

export async function isCurrentUserSysOperator(): Promise<boolean> {
  return (await getSysOperatorSession()) != null;
}

async function staffGrantsOf(userId: string): Promise<Set<string>> {
  const rows = await db
    .select({ permission: staffPermission.permission })
    .from(staffPermission)
    .where(eq(staffPermission.userId, userId));
  return new Set(rows.map((r) => r.permission));
}

/** LE gate du back-office (spec §6). Throw si non-opérateur, 2FA absent, ou
 *  permission non détenue. Lecture DB à chaque appel : une révocation prend
 *  effet immédiatement. */
export async function assertSysPermission(perm: SysPermission): Promise<void> {
  const op = await getSysOperatorSession();
  const grants =
    op?.kind === "staff-sys"
      ? await staffGrantsOf(op.userId)
      : new Set<string>();
  const verdict = resolveSysPermission(op, perm, grants);
  if (verdict === "2fa-required") {
    throw new Error("Forbidden: 2FA required for the admin surface");
  }
  if (verdict !== "ok") {
    throw new Error(`Forbidden: missing sys permission ${perm}`);
  }
}

/** Permissions effectives de l'opérateur courant (nav/boutons de l'UI admin).
 *  Vide pour un non-opérateur ou sans 2FA. */
export async function currentSysPermissions(): Promise<Set<SysPermission>> {
  const op = await getSysOperatorSession();
  if (!op?.twoFactorEnabled) return new Set();
  if (op.kind === "admin-sys") {
    return new Set<SysPermission>([
      ...GRANTABLE_SYS_PERMISSIONS,
      ...RESERVED_SYS_PERMISSIONS,
    ]);
  }
  const grants = await staffGrantsOf(op.userId);
  return new Set(
    [...grants].filter(isGrantableSysPermission),
  ) as Set<SysPermission>;
}
