/**
 * سرویس کنترل دسترسی مبتنی بر نقش (RBAC) با پشتیبانی از دامنه (Scope).
 *
 * نکته امنیتی کلیدی: یک نقش سازمانی فقط داخل همان سازمان معتبر است. بنابراین
 * کارمند سازمان A حتی اگر مجوز report.respond داشته باشد، نمی‌تواند روی پرونده
 * سازمان B اقدام کند. این بررسی در تابع can() با پارامتر scope انجام می‌شود.
 */
import { and, eq, inArray } from 'drizzle-orm';
import { getDb } from '../../database/client.js';
import { roles, rolePermissions, permissions, userRoles } from '../../database/schema/index.js';
import type { PermissionKey } from './catalog.js';

export type Grant = {
  roleKey: string;
  scopeType: 'global' | 'organization' | 'department' | 'business';
  scopeId: string | null;
  permissions: Set<string>;
};

export type Principal = {
  userId: string;
  displayName: string;
  email: string;
  grants: Grant[];
};

export type ScopeRef = { organizationId?: string | null; businessId?: string | null };

/** بارگذاری همه نقش‌ها و دسترسی‌های مؤثر یک کاربر */
export async function loadGrants(userId: string): Promise<Grant[]> {
  const db = getDb();
  const rows = await db
    .select({
      roleKey: roles.key,
      roleId: roles.id,
      scopeType: userRoles.scopeType,
      scopeId: userRoles.scopeId,
    })
    .from(userRoles)
    .innerJoin(roles, eq(roles.id, userRoles.roleId))
    .where(eq(userRoles.userId, userId));

  if (rows.length === 0) return [];

  const permRows = await db
    .select({ roleId: rolePermissions.roleId, key: permissions.key })
    .from(rolePermissions)
    .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId))
    .where(inArray(rolePermissions.roleId, rows.map((r) => r.roleId)));

  const byRole = new Map<string, Set<string>>();
  for (const p of permRows) {
    if (!byRole.has(p.roleId)) byRole.set(p.roleId, new Set());
    byRole.get(p.roleId)!.add(p.key);
  }

  return rows.map((r) => ({
    roleKey: r.roleKey,
    scopeType: r.scopeType as Grant['scopeType'],
    scopeId: r.scopeId,
    permissions: byRole.get(r.roleId) ?? new Set<string>(),
  }));
}

/**
 * بررسی مجوز.
 * اگر scope داده شود، نقش‌های سازمانی/کسب‌وکاری فقط وقتی به حساب می‌آیند که
 * scopeId آن‌ها دقیقاً با موجودیت هدف یکی باشد.
 */
export function can(principal: Principal | null, permission: PermissionKey | string, scope?: ScopeRef): boolean {
  if (!principal) return false;
  for (const grant of principal.grants) {
    if (!grant.permissions.has(permission)) continue;
    if (grant.scopeType === 'global') return true;
    if (!scope) continue;
    if (grant.scopeType === 'organization' && scope.organizationId && grant.scopeId === scope.organizationId) return true;
    if (grant.scopeType === 'department' && scope.organizationId && grant.scopeId === scope.organizationId) return true;
    if (grant.scopeType === 'business' && scope.businessId && grant.scopeId === scope.businessId) return true;
  }
  return false;
}

export function hasRole(principal: Principal | null, roleKey: string, scopeId?: string | null): boolean {
  if (!principal) return false;
  return principal.grants.some((g) => g.roleKey === roleKey && (scopeId === undefined || g.scopeId === scopeId));
}

/** فهرست سازمان‌هایی که کاربر در آن‌ها نقش دارد (پایه‌ی جداسازی چند-مستاجری) */
export function scopedOrganizationIds(principal: Principal | null): string[] {
  if (!principal) return [];
  return principal.grants
    .filter((g) => (g.scopeType === 'organization' || g.scopeType === 'department') && g.scopeId)
    .map((g) => g.scopeId!) as string[];
}

export function scopedBusinessIds(principal: Principal | null): string[] {
  if (!principal) return [];
  return principal.grants.filter((g) => g.scopeType === 'business' && g.scopeId).map((g) => g.scopeId!) as string[];
}

export function effectivePermissions(principal: Principal | null): string[] {
  if (!principal) return [];
  const set = new Set<string>();
  for (const g of principal.grants) for (const p of g.permissions) set.add(p);
  return [...set];
}

/** انتساب نقش به کاربر (استفاده در seed و پنل مدیریت) */
export async function assignRole(userId: string, roleKey: string, scope?: { type: Grant['scopeType']; id?: string | null }, grantedBy?: string) {
  const db = getDb();
  const [role] = await db.select().from(roles).where(eq(roles.key, roleKey));
  if (!role) throw new Error(`نقش ${roleKey} تعریف نشده است.`);
  const scopeType = scope?.type ?? 'global';
  const scopeId = scope?.id ?? null;
  const existing = await db
    .select()
    .from(userRoles)
    .where(and(eq(userRoles.userId, userId), eq(userRoles.roleId, role.id), eq(userRoles.scopeType, scopeType)));
  if (existing.some((e) => e.scopeId === scopeId)) return;
  await db.insert(userRoles).values({ userId, roleId: role.id, scopeType, scopeId, grantedBy: grantedBy ?? null });
}
