import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { ConfigService } from '@nestjs/config';
import { extname } from 'path';
import { Connection, Types } from 'mongoose';
import { ModerationStatus } from '../../common/enums/moderation-status.enum';
import { ExperienceLevel } from '../../common/enums/experience-level.enum';
import { MusicRole } from '../../common/enums/music-role.enum';
import { hashValue } from '../../common/utils/hash.util';
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
import { resolveMongoSortDirection } from '../../common/utils/sort.util';
import { ManagedStorageService } from '../../infrastructure/storage/managed-storage.service';
import { AuditService } from '../audit/audit.service';
import { UserRole } from '../../common/enums/user-role.enum';
import { CreateUserDto } from './dto/create-user.dto';
import { AdminDisableUserDto } from './dto/admin-disable-user.dto';
import { CreateAdminDto } from './dto/create-admin.dto';
import { MusicSetupDto } from './dto/music-setup.dto';
import { ProfileSetupDto } from './dto/profile-setup.dto';
import { TalentDiscoverQueryDto } from './dto/talent-discover-query.dto';
import { UpdateUserRoleDto } from './dto/update-user-role.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserQueryDto } from './dto/user-query.dto';
import { UsersRepository } from './users.repository';
import { UserDocument } from './schemas/user.schema';

type UploadedImageFile = {
  mimetype?: string;
  size: number;
  buffer: Buffer;
  originalname?: string;
};

@Injectable()
export class UsersService {
  constructor(
    @InjectConnection() private readonly connection: Connection,
    private readonly usersRepository: UsersRepository,
    private readonly auditService: AuditService,
    private readonly configService: ConfigService,
    private readonly storageService: ManagedStorageService,
  ) {}

  async create(dto: CreateUserDto & { password: string; role?: UserRole }): Promise<UserDocument> {
    const existing = await this.usersRepository.findOne({
      $or: [{ email: dto.email.toLowerCase() }, { username: dto.username.toLowerCase() }],
    });

    if (existing) {
      throw new BadRequestException('Email or username already exists');
    }

    const roles = dto.musicRoles ?? [];

    return this.usersRepository.create({
      ...dto,
      email: dto.email.toLowerCase(),
      username: dto.username.toLowerCase(),
      stageName: dto.stageName ?? '',
      bio: dto.bio ?? '',
      avatar: dto.avatar ?? '',
      coverImage: dto.coverImage ?? '',
      location: dto.location ?? '',
      latitude: dto.latitude,
      longitude: dto.longitude,
      isPrivate: dto.isPrivate ?? false,
      isVerified: dto.isVerified ?? false,
      isEmailVerified: dto.isEmailVerified ?? false,
      musicRoles: roles,
      experienceLevel: dto.experienceLevel ?? ExperienceLevel.BEGINNER,
      musicGenres: dto.musicGenres ?? [],
      favoriteInstruments: dto.favoriteInstruments ?? [],
      favoriteMaqamat: dto.favoriteMaqamat ?? [],
      role: dto.role ?? UserRole.USER,
      isDisabled: false,
      disabledReason: '',
      authProvider: 'local',
    });
  }

  async createAdminBySuperAdmin(
    superAdminIdentifier: string,
    dto: CreateAdminDto,
  ): Promise<UserDocument> {
    if (dto.password !== dto.confirmPassword) {
      throw new BadRequestException('Password confirmation does not match');
    }

    const saltRounds = this.configService.get<number>('security.bcryptSaltRounds', { infer: true });
    const passwordHash = await hashValue(dto.password, saltRounds);

    const admin = await this.create({
      name: dto.name,
      username: dto.username,
      email: dto.email,
      password: passwordHash,
      role: UserRole.ADMIN,
      isVerified: true,
    });

    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'admin_create',
      'user',
      admin.id,
      { role: UserRole.ADMIN },
    );

    return admin;
  }

  async listAdminsBySuperAdmin(query: UserQueryDto): Promise<{
    items: UserDocument[];
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  }> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const filter: Record<string, unknown> = {
      role: UserRole.ADMIN,
    };

    if (query.q) {
      filter.$or = [
        { name: { $regex: query.q, $options: 'i' } },
        { username: { $regex: query.q, $options: 'i' } },
        { email: { $regex: query.q, $options: 'i' } },
      ];
    }

    if (typeof query.isVerified === 'boolean') {
      filter.isVerified = query.isVerified;
    }

    const direction = resolveMongoSortDirection(query.sortOrder);
    const sortField = query.sortBy ?? 'createdAt';
    const sort = { [sortField]: direction } as Record<string, 1 | -1>;

    const [items, total] = await Promise.all([
      this.usersRepository.findMany(filter, skip, limit, sort),
      this.usersRepository.count(filter),
    ]);

    return buildPaginatedResponse(items, {
      page,
      limit,
      total,
      offset: skip,
    });
  }

  async updateAdminBySuperAdmin(
    superAdminIdentifier: string,
    adminUserId: string,
    dto: UpdateUserDto,
  ): Promise<UserDocument> {
    await this.assertTargetIsAdmin(adminUserId);
    const payload = await this.prepareManagedUserUpdatePayload(adminUserId, dto);
    const updated = await this.updateUserAndCleanupReplacedImages(adminUserId, payload);

    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'admin_update',
      'user',
      adminUserId,
      { fields: Object.keys(payload) },
    );

    return updated;
  }

  async deleteAdminBySuperAdmin(superAdminIdentifier: string, adminUserId: string): Promise<void> {
    const admin = await this.assertTargetIsAdmin(adminUserId);
    await this.deleteUserRelatedData(adminUserId, {
      avatarUrl: admin.avatar ?? '',
      coverImageUrl: admin.coverImage ?? '',
    });

    await this.usersRepository.deleteById(adminUserId);

    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'admin_delete',
      'user',
      adminUserId,
    );
  }

  async updateUserRoleBySuperAdmin(
    superAdminIdentifier: string,
    targetUserId: string,
    dto: UpdateUserRoleDto,
  ): Promise<UserDocument> {
    if (dto.role === UserRole.SUPERADMIN) {
      throw new BadRequestException('Cannot assign superadmin role via API');
    }

    await this.assertTargetIsManagedUser(targetUserId);
    const updated = await this.usersRepository.updateById(targetUserId, { role: dto.role });
    if (!updated) {
      throw new NotFoundException('User not found');
    }

    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'user_role_update',
      'user',
      targetUserId,
      { role: dto.role },
    );

    return updated;
  }

  async findByIdOrFail(userId: string): Promise<UserDocument> {
    const user = await this.usersRepository.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

  async findPublicByIdOrFail(userId: string): Promise<UserDocument> {
    const user = await this.findByIdOrFail(userId);
    if (user.isDisabled) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

  async findPublicByIdForViewer(userId: string, viewerUserId: string) {
    const user = await this.findPublicByIdOrFail(userId);
    const isFollowing =
      viewerUserId === userId
        ? false
        : (await this.connection.collection('follows').countDocuments({
            followerId: new Types.ObjectId(viewerUserId),
            followingId: new Types.ObjectId(userId),
          })) > 0;

    return {
      ...user.toObject(),
      isFollowing,
      following: isFollowing,
      isOwnProfile: viewerUserId === userId,
    };
  }

  async getPresence(userId: string) {
    const user = await this.findPublicByIdOrFail(userId);
    return {
      userId: user.id,
      isOnline: user.isOnline ?? false,
      lastSeenAt: user.lastSeenAt ?? null,
    };
  }

  async setPresence(userId: string, isOnline: boolean): Promise<void> {
    if (!Types.ObjectId.isValid(userId)) {
      return;
    }
    await this.usersRepository.setPresence(userId, isOnline, new Date());
  }

  async findByEmailWithPassword(email: string): Promise<UserDocument | null> {
    return this.usersRepository.findOneWithPassword({ email: email.toLowerCase() });
  }

  async findByEmail(email: string): Promise<UserDocument | null> {
    return this.usersRepository.findOne({ email: email.toLowerCase() });
  }

  async findByUsername(username: string): Promise<UserDocument | null> {
    return this.usersRepository.findOne({ username: username.toLowerCase() });
  }

  async findByGoogleId(googleId: string): Promise<UserDocument | null> {
    return this.usersRepository.findOne({ googleId });
  }

  async linkGoogleAccount(userId: string, googleId: string, avatar?: string): Promise<UserDocument> {
    const currentUser = await this.findByIdOrFail(userId);
    const user = await this.updateUserAndCleanupReplacedImages(
      userId,
      {
        googleId,
        authProvider: 'google',
        ...(avatar ? { avatar } : {}),
      },
      { currentUser },
    );

    return user;
  }

  async updateProfile(
    userId: string,
    dto: UpdateUserDto,
    avatarFile?: UploadedImageFile,
    coverImageFile?: UploadedImageFile,
  ): Promise<UserDocument> {
    const currentUser = await this.findByIdOrFail(userId);
    const payload: Record<string, unknown> = { ...dto };
    const uploadedImageUrls = await this.attachUploadedProfileImages(
      payload,
      avatarFile,
      coverImageFile,
    );

    return this.updateUserAndCleanupReplacedImages(userId, payload, {
      currentUser,
      uploadedImageUrls,
    });
  }

  async updatePassword(userId: string, passwordHash: string): Promise<void> {
    const updated = await this.usersRepository.updateById(userId, { password: passwordHash });
    if (!updated) {
      throw new NotFoundException('User not found');
    }
  }

  async markEmailVerified(userId: string): Promise<void> {
    const updated = await this.usersRepository.updateById(userId, { isEmailVerified: true });
    if (!updated) {
      throw new NotFoundException('User not found');
    }
  }

  async findUserByIdForSuperAdmin(targetUserId: string): Promise<UserDocument> {
    return this.assertTargetIsManagedUser(targetUserId);
  }

  async updateUserBySuperAdmin(
    superAdminIdentifier: string,
    targetUserId: string,
    dto: UpdateUserDto,
  ): Promise<UserDocument> {
    await this.assertTargetIsManagedUser(targetUserId);
    const payload = await this.prepareManagedUserUpdatePayload(targetUserId, dto);
    const user = await this.updateUserAndCleanupReplacedImages(targetUserId, payload);
    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'user_update',
      'user',
      targetUserId,
      { fields: Object.keys(payload) },
    );
    return user;
  }

  private async prepareManagedUserUpdatePayload(
    targetUserId: string,
    dto: UpdateUserDto,
  ): Promise<Record<string, unknown>> {
    const payload: Record<string, unknown> = { ...dto };

    if (typeof dto.email === 'string') {
      const normalizedEmail = dto.email.trim().toLowerCase();
      if (!normalizedEmail) {
        throw new BadRequestException('Email is required');
      }

      const existingUser = await this.usersRepository.findOne({
        email: normalizedEmail,
        _id: { $ne: targetUserId },
      });
      if (existingUser) {
        throw new BadRequestException('Email already exists');
      }

      payload.email = normalizedEmail;
    }

    if (typeof dto.username === 'string') {
      const normalizedUsername = dto.username.trim().toLowerCase();
      if (!normalizedUsername) {
        throw new BadRequestException('Username is required');
      }

      const existingUser = await this.usersRepository.findOne({
        username: normalizedUsername,
        _id: { $ne: targetUserId },
      });
      if (existingUser) {
        throw new BadRequestException('Username already exists');
      }

      payload.username = normalizedUsername;
    }

    return payload;
  }

  async updateProfileSetup(
    userId: string,
    dto: ProfileSetupDto,
    avatarFile?: UploadedImageFile,
    coverImageFile?: UploadedImageFile,
  ): Promise<UserDocument> {
    if (!Number.isFinite(dto.latitude) || !Number.isFinite(dto.longitude)) {
      throw new BadRequestException('latitude and longitude are required');
    }

    const currentUser = await this.findByIdOrFail(userId);
    const payload: Record<string, unknown> = { ...dto };
    const uploadedImageUrls = await this.attachUploadedProfileImages(
      payload,
      avatarFile,
      coverImageFile,
    );

    return this.updateUserAndCleanupReplacedImages(userId, payload, {
      currentUser,
      uploadedImageUrls,
    });
  }

  private async saveAvatarFile(avatarFile: UploadedImageFile): Promise<string> {
    return this.saveProfileImageFile(avatarFile, {
      folderSegment: 'avatars',
      fileFieldName: 'avatarFile',
      fileNamePrefix: 'avatar',
    });
  }

  private async saveCoverImageFile(coverImageFile: UploadedImageFile): Promise<string> {
    return this.saveProfileImageFile(coverImageFile, {
      folderSegment: 'profile-covers',
      fileFieldName: 'coverImageFile',
      fileNamePrefix: 'cover',
    });
  }

  private async saveProfileImageFile(
    imageFile: UploadedImageFile,
    options: {
      folderSegment: string;
      fileFieldName: 'avatarFile' | 'coverImageFile';
      fileNamePrefix: string;
    },
  ): Promise<string> {
    const extension = this.resolveImageExtension(imageFile);
    const maxSize = 5 * 1024 * 1024;

    if (!extension) {
      throw new BadRequestException(`${options.fileFieldName} must be png, jpg, jpeg, webp, or gif`);
    }

    if (imageFile.size > maxSize) {
      throw new BadRequestException(`${options.fileFieldName} size must be 5MB or less`);
    }

    return this.storageService.saveFile({
      folderSegments: [options.folderSegment],
      extension,
      buffer: imageFile.buffer,
      contentType: imageFile.mimetype,
      fileNamePrefix: options.fileNamePrefix,
    });
  }

  private resolveImageExtension(imageFile: UploadedImageFile): string | null {
    const originalExtension = extname(imageFile.originalname ?? '').toLowerCase();
    const allowedExtensions = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);

    if (allowedExtensions.has(originalExtension)) {
      return originalExtension;
    }

    switch (imageFile.mimetype) {
      case 'image/png':
        return '.png';
      case 'image/jpeg':
      case 'image/jpg':
        return '.jpg';
      case 'image/webp':
        return '.webp';
      case 'image/gif':
        return '.gif';
      default:
        return null;
    }
  }

  private async attachUploadedProfileImages(
    payload: Record<string, unknown>,
    avatarFile?: UploadedImageFile,
    coverImageFile?: UploadedImageFile,
  ): Promise<string[]> {
    const uploadedImageUrls: string[] = [];

    if (avatarFile) {
      const uploadedAvatarUrl = await this.saveAvatarFile(avatarFile);
      payload.avatar = uploadedAvatarUrl;
      uploadedImageUrls.push(uploadedAvatarUrl);
    }

    if (coverImageFile) {
      const uploadedCoverImageUrl = await this.saveCoverImageFile(coverImageFile);
      payload.coverImage = uploadedCoverImageUrl;
      uploadedImageUrls.push(uploadedCoverImageUrl);
    }

    return uploadedImageUrls;
  }

  private async updateUserAndCleanupReplacedImages(
    userId: string,
    payload: Record<string, unknown>,
    options?: {
      currentUser?: UserDocument;
      uploadedImageUrls?: string[];
    },
  ): Promise<UserDocument> {
    const currentUser = options?.currentUser ?? (await this.findByIdOrFail(userId));
    const user = await this.usersRepository.updateById(userId, payload);

    if (!user) {
      await Promise.all((options?.uploadedImageUrls ?? []).map((fileUrl) => this.deleteManagedUpload(fileUrl)));
      throw new NotFoundException('User not found');
    }

    await this.cleanupReplacedUserImages(currentUser, payload);
    return user;
  }

  private async cleanupReplacedUserImages(
    currentUser: UserDocument,
    payload: Record<string, unknown>,
  ): Promise<void> {
    const imageFields: Array<'avatar' | 'coverImage'> = ['avatar', 'coverImage'];

    await Promise.all(
      imageFields.map(async (field) => {
        const nextValue = payload[field];
        if (typeof nextValue !== 'string') {
          return;
        }

        const currentValue = (currentUser.get(field) as string | undefined) ?? '';
        if (nextValue === currentValue) {
          return;
        }

        await this.deleteManagedUpload(currentValue);
      }),
    );
  }

  async updateMusicSetup(userId: string, dto: MusicSetupDto): Promise<UserDocument> {
    const payload: Record<string, unknown> = { ...dto };

    const user = await this.usersRepository.updateById(userId, payload);
    if (!user) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

  async disableUserBySuperAdmin(
    superAdminIdentifier: string,
    targetUserId: string,
    dto: AdminDisableUserDto,
  ): Promise<UserDocument> {
    await this.assertTargetIsManagedUser(targetUserId);

    const updated = await this.usersRepository.updateById(targetUserId, {
      isDisabled: true,
      disabledAt: new Date(),
      disabledReason: dto.reason ?? '',
      disabledBy: superAdminIdentifier,
    });
    if (!updated) {
      throw new NotFoundException('User not found');
    }
    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'user_disable',
      'user',
      targetUserId,
      { reason: dto.reason ?? '' },
    );
    return updated;
  }

  async enableUserBySuperAdmin(superAdminIdentifier: string, targetUserId: string): Promise<UserDocument> {
    await this.assertTargetIsManagedUser(targetUserId);
    const updated = await this.usersRepository.updateById(targetUserId, {
      isDisabled: false,
      disabledAt: null,
      disabledReason: '',
      disabledBy: null,
    });
    if (!updated) {
      throw new NotFoundException('User not found');
    }
    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'user_enable',
      'user',
      targetUserId,
    );
    return updated;
  }

  async deleteUserBySuperAdmin(superAdminIdentifier: string, targetUserId: string): Promise<void> {
    const user = await this.assertTargetIsManagedUser(targetUserId);
    await this.deleteUserRelatedData(targetUserId, {
      avatarUrl: user.avatar ?? '',
      coverImageUrl: user.coverImage ?? '',
    });
    await this.usersRepository.deleteById(targetUserId);
    await this.auditService.logSuperAdminAction(
      superAdminIdentifier,
      'user_delete',
      'user',
      targetUserId,
    );
  }

  async searchUsers(query: UserQueryDto): Promise<{
    items: UserDocument[];
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  }> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const filter = this.buildPublicUserFilter(query);

    const direction = resolveMongoSortDirection(query.sortOrder);
    const sortField = query.sortBy ?? 'createdAt';
    const sort = { [sortField]: direction } as Record<string, 1 | -1>;

    const [items, total] = await Promise.all([
      this.usersRepository.findMany(filter, skip, limit, sort),
      this.usersRepository.count(filter),
    ]);

    return buildPaginatedResponse(items, {
      page,
      limit,
      total,
      offset: skip,
    });
  }

  async discoverTalents(query: TalentDiscoverQueryDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 8;
    const skip = (page - 1) * limit;

    const baseQuery: UserQueryDto = {
      ...query,
      hasAvatar: query.hasAvatarOnly ?? true,
    };
    const filter = this.buildPublicUserFilter(baseQuery, {
      forcePublicTalentsOnly: true,
    });
    const direction = resolveMongoSortDirection(query.sortOrder);
    const sortField = query.sortBy ?? 'followersCount';
    const sort = { [sortField]: direction } as Record<string, 1 | -1>;

    const [items, total, roleBuckets] = await Promise.all([
      this.usersRepository.findMany(filter, skip, limit, sort),
      this.usersRepository.count(filter),
      query.includeRoleBuckets === false
        ? Promise.resolve([])
        : this.buildTalentRoleBuckets(baseQuery),
    ]);

    const result = buildPaginatedResponse(items, {
      page,
      limit,
      total,
      offset: skip,
    });

    return {
      ...result,
      roleBuckets,
      activeRole: query.musicRole ?? null,
    };
  }

  async discoverTalentsForSuperAdmin(query: TalentDiscoverQueryDto) {
    return this.discoverTalents(query);
  }

  async getProfileOverview(userId: string, viewerUserId: string) {
    const user = await this.findPublicByIdOrFail(userId);
    const objectUserId = new Types.ObjectId(userId);
    const postsCollection = this.connection.collection('posts');
    const followsCollection = this.connection.collection('follows');

    const [reelsCount, audioCount, imageCount, textCount, collaborationsCount, followingState] =
      await Promise.all([
        postsCollection.countDocuments({
          authorId: objectUserId,
          postType: 'video',
          isDeleted: false,
          moderationStatus: { $ne: ModerationStatus.HIDDEN },
        }),
        postsCollection.countDocuments({
          authorId: objectUserId,
          postType: 'audio',
          isDeleted: false,
          moderationStatus: { $ne: ModerationStatus.HIDDEN },
        }),
        postsCollection.countDocuments({
          authorId: objectUserId,
          postType: 'image',
          isDeleted: false,
          moderationStatus: { $ne: ModerationStatus.HIDDEN },
        }),
        postsCollection.countDocuments({
          authorId: objectUserId,
          postType: 'text',
          isDeleted: false,
          moderationStatus: { $ne: ModerationStatus.HIDDEN },
        }),
        postsCollection.countDocuments({
          isDeleted: false,
          moderationStatus: { $ne: ModerationStatus.HIDDEN },
          $or: [
            { authorId: objectUserId, 'taggedUserIds.0': { $exists: true } },
            { authorId: { $ne: objectUserId }, taggedUserIds: objectUserId },
          ],
        }),
        viewerUserId === userId
          ? Promise.resolve(false)
          : followsCollection.countDocuments({
              followerId: new Types.ObjectId(viewerUserId),
              followingId: objectUserId,
            }).then((count) => count > 0),
      ]);

    return {
      user,
      stats: {
        followersCount: user.followersCount ?? 0,
        followingCount: user.followingCount ?? 0,
        postsCount: user.postsCount ?? 0,
        collaborationsCount,
      },
      contentCounts: {
        reels: reelsCount,
        audio: audioCount,
        image: imageCount,
        text: textCount,
        other: imageCount + textCount,
      },
      tabs: [
        { key: 'reels', postType: 'video', count: reelsCount },
        { key: 'audio', postType: 'audio', count: audioCount },
        { key: 'other', count: imageCount + textCount },
      ],
      viewerState: {
        isOwnProfile: viewerUserId === userId,
        following: followingState,
        isFollowing: followingState,
        canMessage: viewerUserId !== userId,
      },
    };
  }

  async getProfileOverviewForSuperAdmin(userId: string) {
    const overview = await this.getProfileOverview(userId, userId);
    return {
      ...overview,
      viewerState: {
        isOwnProfile: false,
        following: false,
        isFollowing: false,
        canMessage: false,
      },
    };
  }

  async searchUsersForSuperAdmin(query: UserQueryDto): Promise<{
    items: UserDocument[];
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  }> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const filter = this.buildAdminUserSearchFilter(query);
    const direction = resolveMongoSortDirection(query.sortOrder);
    const sortField = query.sortBy ?? 'createdAt';
    const sort = { [sortField]: direction } as Record<string, 1 | -1>;

    const [items, total] = await Promise.all([
      this.usersRepository.findMany(filter, skip, limit, sort),
      this.usersRepository.count(filter),
    ]);

    return buildPaginatedResponse(items, {
      page,
      limit,
      total,
      offset: skip,
    });
  }

  private async assertTargetIsAdmin(userId: string): Promise<UserDocument> {
    const user = await this.findByIdOrFail(userId);
    if (user.role !== UserRole.ADMIN) {
      throw new BadRequestException('Target user is not an admin');
    }
    return user;
  }

  private async assertTargetIsManagedUser(userId: string): Promise<UserDocument> {
    const user = await this.findByIdOrFail(userId);
    if (user.role === UserRole.SUPERADMIN) {
      throw new ForbiddenException('Superadmin accounts cannot be managed through this endpoint');
    }
    return user;
  }

  private async deleteUserRelatedData(
    userId: string,
    options?: {
      avatarUrl?: string;
      coverImageUrl?: string;
    },
  ): Promise<void> {
    if (!Types.ObjectId.isValid(userId)) {
      return;
    }

    const objectUserId = new Types.ObjectId(userId);

    const postsCollection = this.connection.collection('posts');
    const commentsCollection = this.connection.collection('comments');
    const likesCollection = this.connection.collection('likes');
    const savesCollection = this.connection.collection('saves');
    const followsCollection = this.connection.collection('follows');
    const notificationsCollection = this.connection.collection('notifications');
    const conversationsCollection = this.connection.collection('conversations');
    const messagesCollection = this.connection.collection('messages');
    const chatBlocksCollection = this.connection.collection('chatblocks');
    const marketplaceCollection = this.connection.collection('instruments');
    const refreshTokensCollection = this.connection.collection('refreshtokens');
    const passwordResetCodesCollection = this.connection.collection('passwordresetcodes');
    const emailVerificationCodesCollection = this.connection.collection('emailverificationcodes');

    const [adminPosts, ownComments, adminConversations, ownMessages, ownInstruments] = await Promise.all([
      postsCollection.find({ authorId: objectUserId }).project({ _id: 1, videoUrl: 1, audioUrl: 1 }).toArray(),
      commentsCollection.find({ authorId: objectUserId }).project({ _id: 1 }).toArray(),
      conversationsCollection.find({ participantIds: objectUserId }).project({ _id: 1 }).toArray(),
      messagesCollection.find({ senderId: objectUserId }).project({ _id: 1, mediaUrl: 1 }).toArray(),
      marketplaceCollection.find({ ownerAdminId: objectUserId }).project({ _id: 1, imageUrls: 1 }).toArray(),
    ]);

    const postIds = adminPosts.map((post) => post._id as Types.ObjectId);
    const ownCommentIds = ownComments.map((comment) => comment._id as Types.ObjectId);
    const conversationIds = adminConversations.map((conversation) => conversation._id as Types.ObjectId);

    const postComments = postIds.length
      ? await commentsCollection.find({ postId: { $in: postIds } }).project({ _id: 1 }).toArray()
      : [];
    const postCommentIds = postComments.map((comment) => comment._id as Types.ObjectId);
    const commentIds = [...ownCommentIds, ...postCommentIds];

    const conversationMessages = conversationIds.length
      ? await messagesCollection
          .find({ conversationId: { $in: conversationIds } })
          .project({ _id: 1, mediaUrl: 1 })
          .toArray()
      : [];

    const localFilesToDelete = new Set<string>();
    if (options?.avatarUrl) {
      localFilesToDelete.add(options.avatarUrl);
    }
    if (options?.coverImageUrl) {
      localFilesToDelete.add(options.coverImageUrl);
    }

    for (const post of adminPosts) {
      if (typeof post.videoUrl === 'string') {
        localFilesToDelete.add(post.videoUrl);
      }
      if (typeof post.audioUrl === 'string') {
        localFilesToDelete.add(post.audioUrl);
      }
    }

    for (const message of [...ownMessages, ...conversationMessages]) {
      if (typeof message.mediaUrl === 'string') {
        localFilesToDelete.add(message.mediaUrl);
      }
    }

    for (const instrument of ownInstruments) {
      if (Array.isArray(instrument.imageUrls)) {
        for (const imageUrl of instrument.imageUrls) {
          if (typeof imageUrl === 'string') {
            localFilesToDelete.add(imageUrl);
          }
        }
      }
    }

    const likesDeleteOr: Record<string, unknown>[] = [{ userId: objectUserId }];
    if (postIds.length) {
      likesDeleteOr.push({ targetType: 'post', targetId: { $in: postIds } });
    }
    if (commentIds.length) {
      likesDeleteOr.push({ targetType: 'comment', targetId: { $in: commentIds } });
    }

    const savesDeleteOr: Record<string, unknown>[] = [{ userId: objectUserId }];
    if (postIds.length) {
      savesDeleteOr.push({ postId: { $in: postIds } });
    }

    await Promise.all([
      likesCollection.deleteMany({ $or: likesDeleteOr }),
      savesCollection.deleteMany({ $or: savesDeleteOr }),
      followsCollection.deleteMany({ $or: [{ followerId: objectUserId }, { followingId: objectUserId }] }),
      notificationsCollection.deleteMany({ $or: [{ recipientId: objectUserId }, { actorId: objectUserId }] }),
      chatBlocksCollection.deleteMany({ $or: [{ blockerId: objectUserId }, { blockedId: objectUserId }] }),
      refreshTokensCollection.deleteMany({ userId: objectUserId }),
      passwordResetCodesCollection.deleteMany({ userId: objectUserId }),
      emailVerificationCodesCollection.deleteMany({ userId: objectUserId }),
      conversationIds.length ? messagesCollection.deleteMany({ conversationId: { $in: conversationIds } }) : null,
      ownMessages.length ? messagesCollection.deleteMany({ senderId: objectUserId }) : null,
      conversationIds.length ? conversationsCollection.deleteMany({ _id: { $in: conversationIds } }) : null,
      commentIds.length ? commentsCollection.deleteMany({ _id: { $in: commentIds } }) : null,
      postIds.length ? postsCollection.deleteMany({ _id: { $in: postIds } }) : null,
      marketplaceCollection.deleteMany({ ownerAdminId: objectUserId }),
    ]);

    await Promise.all([...localFilesToDelete].map((fileUrl) => this.deleteManagedUpload(fileUrl)));
  }

  private async deleteManagedUpload(fileUrl: string): Promise<void> {
    await this.storageService.deleteFile(fileUrl);
  }

  private buildPublicUserFilter(
    query: Pick<UserQueryDto, 'q' | 'isVerified' | 'musicRole' | 'experienceLevel' | 'isPrivate' | 'hasAvatar'>,
    options?: { forcePublicTalentsOnly?: boolean },
  ): Record<string, unknown> {
    const clauses: Record<string, unknown>[] = [{ isDisabled: false }];

    if (query.q?.trim()) {
      clauses.push({
        $or: [
          { name: { $regex: query.q.trim(), $options: 'i' } },
          { username: { $regex: query.q.trim(), $options: 'i' } },
          { stageName: { $regex: query.q.trim(), $options: 'i' } },
          { bio: { $regex: query.q.trim(), $options: 'i' } },
        ],
      });
    }

    if (typeof query.isVerified === 'boolean') {
      clauses.push({ isVerified: query.isVerified });
    }

    if (query.musicRole) {
      clauses.push({ musicRoles: query.musicRole });
    }

    if (query.experienceLevel) {
      clauses.push({ experienceLevel: query.experienceLevel });
    }

    if (typeof query.isPrivate === 'boolean') {
      clauses.push({ isPrivate: query.isPrivate });
    }

    if (typeof query.hasAvatar === 'boolean') {
      clauses.push(
        query.hasAvatar
          ? { avatar: { $ne: '' } }
          : {
              $or: [{ avatar: '' }, { avatar: { $exists: false } }],
            },
      );
    }

    if (options?.forcePublicTalentsOnly) {
      clauses.push({ role: UserRole.USER });
      clauses.push({ isPrivate: false });
      clauses.push({ musicRoles: { $exists: true, $ne: [] } });
    }

    if (clauses.length === 1) {
      return clauses[0];
    }

    return { $and: clauses };
  }

  private async buildTalentRoleBuckets(
    query: Pick<UserQueryDto, 'q' | 'isVerified' | 'experienceLevel' | 'isPrivate' | 'hasAvatar'>,
  ) {
    const roles = Object.values(MusicRole);
    return Promise.all(
      roles.map(async (role) => ({
        role,
        count: await this.usersRepository.count(
          this.buildPublicUserFilter(
            {
              ...query,
              musicRole: role,
            },
            { forcePublicTalentsOnly: true },
          ),
        ),
      })),
    );
  }

  private buildAdminUserSearchFilter(query: UserQueryDto): Record<string, unknown> {
    const clauses: Record<string, unknown>[] = [{ role: { $ne: UserRole.SUPERADMIN } }];

    if (query.q?.trim()) {
      clauses.push({
        $or: [
          { name: { $regex: query.q.trim(), $options: 'i' } },
          { username: { $regex: query.q.trim(), $options: 'i' } },
          { email: { $regex: query.q.trim(), $options: 'i' } },
          { stageName: { $regex: query.q.trim(), $options: 'i' } },
          { bio: { $regex: query.q.trim(), $options: 'i' } },
        ],
      });
    }

    if (typeof query.isVerified === 'boolean') {
      clauses.push({ isVerified: query.isVerified });
    }

    if (query.musicRole) {
      clauses.push({ musicRoles: query.musicRole });
    }

    if (query.experienceLevel) {
      clauses.push({ experienceLevel: query.experienceLevel });
    }

    if (typeof query.isPrivate === 'boolean') {
      clauses.push({ isPrivate: query.isPrivate });
    }

    if (typeof query.hasAvatar === 'boolean') {
      clauses.push(
        query.hasAvatar
          ? { avatar: { $ne: '' } }
          : {
              $or: [{ avatar: '' }, { avatar: { $exists: false } }],
            },
      );
    }

    if (clauses.length === 1) {
      return clauses[0];
    }

    return { $and: clauses };
  }
}
