import type { NextFunction, Request, Response } from "express";
import { audit } from "../lib/audit.js";
import { forbidden, unauthorized } from "../lib/errors.js";

/** Deny-by-default authorization gate; every denial is written to the audit trail (AUD-REQ-003)
 *  BEFORE the rejection reaches the client, so the trail can never lag the response. */
export function requirePermission(permission: string) {
  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    if (!req.user) return next(unauthorized("الجلسة غير صالحة، يرجى تسجيل الدخول"));
    if (!req.user.permissions.has(permission)) {
      await audit({
        actorUserId: req.user.id,
        action: "authz.denied",
        entityType: "endpoint",
        context: { permission, method: req.method, path: req.path },
        ip: req.ip ?? null,
        userAgent: req.headers["user-agent"] ?? null,
      });
      return next(forbidden("لا تملك الصلاحية للوصول إلى هذا المورد"));
    }
    next();
  };
}

export function requireAnyPermission(...permissions: string[]) {
  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    if (!req.user) return next(unauthorized("الجلسة غير صالحة، يرجى تسجيل الدخول"));
    const has = permissions.some((p) => req.user!.permissions.has(p));
    if (!has) {
      await audit({
        actorUserId: req.user.id,
        action: "authz.denied",
        entityType: "endpoint",
        context: { permissions, method: req.method, path: req.path },
        ip: req.ip ?? null,
        userAgent: req.headers["user-agent"] ?? null,
      });
      return next(forbidden("لا تملك الصلاحية للوصول إلى هذا المورد"));
    }
    next();
  };
}

/**
 * Resolve the governorate scope applied inside SQL queries (horizontal-escalation guard).
 * Returns null = unrestricted (national role); [] = scoped role with no scope records (no access);
 * otherwise the list of governorate ids the user may touch.
 */
export function governorateScope(req: Request): number[] | null {
  const user = req.user;
  if (!user) return [];
  let scope: number[] | null = null;
  for (const role of user.roles) {
    if (role.requiresScope) {
      const ids = role.governorateIds;
      scope = scope === null ? [...ids] : scope.filter((id) => ids.includes(id));
    }
  }
  return scope;
}
