import { clerkClient } from "@clerk/nextjs/server";
import log from "./logger";

/**
 * User roles available in the application
 */
export type UserRole = "admin" | "trainer" | "client";

/**
 * Set a user's role in Clerk public metadata
 * This will trigger a webhook that syncs the role to the database
 *
 * @param userId - Clerk user ID
 * @param role - Role to assign (admin, trainer, or client)
 * @returns Updated user object
 *
 * @example
 * await setUserRole('user_abc123', 'admin');
 */
export async function setUserRole(userId: string, role: UserRole) {
  const client = await clerkClient();

  return await client.users.updateUser(userId, {
    publicMetadata: { role },
  });
}

/**
 * Get a user's role from Clerk public metadata
 *
 * @param userId - Clerk user ID
 * @returns User role or null if not set
 *
 * @example
 * const role = await getUserRole('user_abc123');
 * console.log(role); // 'admin' | 'trainer' | 'client' | null
 */
export async function getUserRole(userId: string): Promise<UserRole | null> {
  const client = await clerkClient();
  const user = await client.users.getUser(userId);

  const role = user.publicMetadata?.role as UserRole | undefined;
  return role || null;
}

/**
 * Check if a user has a specific role
 *
 * @param userId - Clerk user ID
 * @param role - Role to check
 * @returns True if user has the role
 *
 * @example
 * const isAdmin = await hasRole('user_abc123', 'admin');
 */
export async function hasRole(
  userId: string,
  role: UserRole,
): Promise<boolean> {
  const userRole = await getUserRole(userId);
  return userRole === role;
}

/**
 * Check if a user is an admin
 *
 * @param userId - Clerk user ID
 * @returns True if user is an admin
 *
 * @example
 * const isAdmin = await isAdmin('user_abc123');
 */
export async function isAdmin(userId: string): Promise<boolean> {
  return hasRole(userId, "admin");
}

/**
 * Check if a user is a trainer
 *
 * @param userId - Clerk user ID
 * @returns True if user is a trainer
 *
 * @example
 * const isTrainer = await isTrainer('user_abc123');
 */
export async function isTrainer(userId: string): Promise<boolean> {
  return hasRole(userId, "trainer");
}

/**
 * Check if a user is a client
 *
 * @param userId - Clerk user ID
 * @returns True if user is a client
 *
 * @example
 * const isClient = await isClient('user_abc123');
 */
export async function isClient(userId: string): Promise<boolean> {
  return hasRole(userId, "client");
}

/**
 * Bulk update roles for multiple users
 *
 * @param userRoles - Array of {userId, role} objects
 * @returns Array of updated users
 *
 * @example
 * await bulkSetUserRoles([
 *   { userId: 'user_abc123', role: 'admin' },
 *   { userId: 'user_def456', role: 'trainer' },
 * ]);
 */
export async function bulkSetUserRoles(
  userRoles: Array<{ userId: string; role: UserRole }>,
): Promise<void> {
  const client = await clerkClient();

  await Promise.all(
    userRoles.map(({ userId, role }) =>
      client.users.updateUser(userId, {
        publicMetadata: { role },
      }),
    ),
  );
}

/**
 * Get all users with a specific role
 *
 * @param role - Role to filter by
 * @returns Array of users with the specified role
 *
 * @example
 * const admins = await getUsersByRole('admin');
 */
export async function getUsersByRole(role: UserRole) {
  const client = await clerkClient();

  // Clerk doesn't support filtering by metadata directly, so we fetch all users
  // and filter client-side. For large user bases, consider caching or database queries.
  const { data: users } = await client.users.getUserList();

  return users.filter(
    (user) => (user.publicMetadata?.role as UserRole) === role,
  );
}

/**
 * Get user count by role
 *
 * @returns Object with counts for each role
 *
 * @example
 * const counts = await getUserCountByRole();
 * console.log(counts); // { admin: 2, trainer: 5, client: 100 }
 */
export async function getUserCountByRole(): Promise<Record<UserRole, number>> {
  const client = await clerkClient();
  const { data: users } = await client.users.getUserList();

  const counts: Record<UserRole, number> = {
    admin: 0,
    trainer: 0,
    client: 0,
  };

  users.forEach((user) => {
    const role = (user.publicMetadata?.role as UserRole) || "client";
    counts[role]++;
  });

  return counts;
}

/**
 * Sync user role between Clerk and database manually
 * Useful for fixing inconsistencies or after manual changes
 *
 * @param userId - Clerk user ID
 * @returns Success status
 *
 * @example
 * await syncUserRole('user_abc123');
 */
export async function syncUserRole(userId: string): Promise<boolean> {
  try {
    const client = await clerkClient();
    const user = await client.users.getUser(userId);

    // Trigger a webhook by updating the user (even with same data)
    await client.users.updateUser(userId, {
      publicMetadata: user.publicMetadata,
    });

    return true;
  } catch (error) {
    log.error("Failed to sync user role", error, { userId });
    return false;
  }
}
