import { db, role, user, userRole } from "@minmon/db";
import { asc, eq } from "drizzle-orm";

import { permissionedProcedure, protectedProcedure, router } from "../index";
import { updateUserRolesSchema } from "./schemas";
import { notFound } from "./utils";

async function getUsersWithRoles() {
  const [users, assignments] = await Promise.all([
    db
      .select({
        id: user.id,
        name: user.name,
        email: user.email,
        emailVerified: user.emailVerified,
        image: user.image,
        createdAt: user.createdAt,
        updatedAt: user.updatedAt,
      })
      .from(user)
      .orderBy(asc(user.name), asc(user.email)),
    db
      .select({
        userId: userRole.userId,
        roleId: role.id,
        roleName: role.name,
      })
      .from(userRole)
      .innerJoin(role, eq(role.id, userRole.roleId))
      .orderBy(asc(role.name)),
  ]);

  return users.map((item) => ({
    ...item,
    roles: assignments
      .filter((assignment) => assignment.userId === item.id)
      .map((assignment) => ({ id: assignment.roleId, name: assignment.roleName })),
  }));
}

export const usersRouter = router({
  me: protectedProcedure.query(({ ctx }) => ({
    user: ctx.session.user,
    roles: ctx.authorization.roles,
    permissions: ctx.authorization.permissions,
  })),

  list: permissionedProcedure("users.read").query(async () => getUsersWithRoles()),

  roles: permissionedProcedure("users.read").query(async () =>
    db
      .select({
        id: role.id,
        name: role.name,
        description: role.description,
      })
      .from(role)
      .orderBy(asc(role.name)),
  ),

  updateRoles: permissionedProcedure("users.assignRoles")
    .input(updateUserRolesSchema)
    .mutation(async ({ input }) => {
      const [existingUser] = await db.select({ id: user.id }).from(user).where(eq(user.id, input.userId)).limit(1);

      if (!existingUser) {
        notFound("User not found");
      }

      const roles = await db.select({ id: role.id }).from(role);
      const validRoleIds = new Set(roles.map((item) => item.id));
      const uniqueRoleIds = Array.from(new Set(input.roleIds));

      const invalidRoleId = uniqueRoleIds.find((roleId) => !validRoleIds.has(roleId));
      if (invalidRoleId) {
        notFound(`Role not found: ${invalidRoleId}`);
      }

      await db.delete(userRole).where(eq(userRole.userId, input.userId));
      await db.insert(userRole).values(
        uniqueRoleIds.map((roleId) => ({
          userId: input.userId,
          roleId,
        })),
      );

      const updated = await getUsersWithRoles();
      return updated.find((item) => item.id === input.userId);
    }),
});
