import {
  db,
  appsTable,
  appPermissionsTable,
  rolePermissionsTable,
  rolesTable,
  userAppOrdersTable,
  userGroupsTable,
  groupAppsTable,
} from "@workspace/db";
import { and, asc, eq, inArray, sql } from "drizzle-orm";
import { getEffectiveRoleIds } from "../middlewares/auth";

/**
 * Returns the apps visible to a given user, applying the same RBAC
 * rules used by GET /apps:
 *   - admins see every app (including inactive)
 *   - non-admins see active apps that are either unrestricted, granted
 *     to one of their roles via app_permissions, or assigned to one of
 *     their groups via group_apps
 *
 * Lives in lib/ (not routes/) so the storage authorization helper
 * (lib/objectAuthz.ts) can reuse the exact same visibility check
 * without depending on the routes layer.
 */
export async function getVisibleAppsForUser(
  userId: number,
): Promise<typeof appsTable.$inferSelect[]> {
  const effectiveRoleIds = await getEffectiveRoleIds(userId);

  const adminRoleRows = effectiveRoleIds.length > 0
    ? await db
        .select({ id: rolesTable.id })
        .from(rolesTable)
        .where(and(inArray(rolesTable.id, effectiveRoleIds), eq(rolesTable.name, "admin")))
    : [];

  const isAdmin = adminRoleRows.length > 0;

  const baseQuery = db
    .select({
      app: appsTable,
      userSort: userAppOrdersTable.sortOrder,
    })
    .from(appsTable)
    .leftJoin(
      userAppOrdersTable,
      sql`${userAppOrdersTable.appId} = ${appsTable.id} and ${userAppOrdersTable.userId} = ${userId}`,
    )
    .$dynamic();

  const orderedRows = await (isAdmin
    ? baseQuery
    : baseQuery.where(eq(appsTable.isActive, true))
  ).orderBy(
    sql`COALESCE(${userAppOrdersTable.sortOrder}, ${appsTable.sortOrder})`,
    asc(appsTable.nameEn),
  );

  const apps = orderedRows.map((r) => r.app);

  if (isAdmin) return apps;

  const userRoleIds = effectiveRoleIds;
  const userPermissionIds = userRoleIds.length > 0
    ? (await db
        .select({ permissionId: rolePermissionsTable.permissionId })
        .from(rolePermissionsTable)
        .where(inArray(rolePermissionsTable.roleId, userRoleIds))
      ).map((r) => r.permissionId)
    : [];

  const appsWithPermissions = await db
    .select({ appId: appPermissionsTable.appId })
    .from(appPermissionsTable);
  const restrictedAppIds = new Set(appsWithPermissions.map((r) => r.appId));

  if (restrictedAppIds.size === 0) return apps;

  const allowedRestrictedAppIds = userPermissionIds.length > 0
    ? (await db
        .select({ appId: appPermissionsTable.appId })
        .from(appPermissionsTable)
        .where(inArray(appPermissionsTable.permissionId, userPermissionIds))
      ).map((r) => r.appId)
    : [];

  const groupGrantedAppIds = (
    await db
      .select({ appId: groupAppsTable.appId })
      .from(groupAppsTable)
      .innerJoin(
        userGroupsTable,
        eq(userGroupsTable.groupId, groupAppsTable.groupId),
      )
      .where(eq(userGroupsTable.userId, userId))
  ).map((r) => r.appId);
  const groupGrantedSet = new Set(groupGrantedAppIds);

  const allowedAppIdSet = new Set(allowedRestrictedAppIds);
  return apps.filter(
    (app) =>
      !restrictedAppIds.has(app.id) ||
      allowedAppIdSet.has(app.id) ||
      groupGrantedSet.has(app.id),
  );
}

