import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ClientSession, FilterQuery, Model, Types, UpdateQuery } from 'mongoose';
import { User, UserDocument } from './schemas/user.schema';

@Injectable()
export class UsersRepository {
  constructor(@InjectModel(User.name) private readonly userModel: Model<UserDocument>) {}

  async create(payload: Partial<User>): Promise<UserDocument> {
    return this.userModel.create(payload);
  }

  async findById(id: string): Promise<UserDocument | null> {
    return this.userModel.findById(id).exec();
  }

  async findByIdWithPassword(id: string): Promise<UserDocument | null> {
    return this.userModel.findById(id).select('+password').exec();
  }

  async findOne(filter: FilterQuery<UserDocument>): Promise<UserDocument | null> {
    return this.userModel.findOne(filter).exec();
  }

  async findOneWithPassword(filter: FilterQuery<UserDocument>): Promise<UserDocument | null> {
    return this.userModel.findOne(filter).select('+password').exec();
  }

  async updateById(id: string, payload: UpdateQuery<UserDocument>): Promise<UserDocument | null> {
    return this.userModel.findByIdAndUpdate(id, payload, { new: true }).exec();
  }

  async deleteById(id: string): Promise<UserDocument | null> {
    return this.userModel.findByIdAndDelete(id).exec();
  }

  async incrementPostsCount(userId: string, delta: 1 | -1, session?: ClientSession): Promise<void> {
    await this.userModel
      .findByIdAndUpdate(userId, { $inc: { postsCount: delta } }, { new: false, session })
      .exec();
  }

  async incrementFollowersCount(
    userId: string,
    delta: 1 | -1,
    session?: ClientSession,
  ): Promise<void> {
    await this.userModel
      .findByIdAndUpdate(userId, { $inc: { followersCount: delta } }, { new: false, session })
      .exec();
  }

  async incrementFollowingCount(
    userId: string,
    delta: 1 | -1,
    session?: ClientSession,
  ): Promise<void> {
    await this.userModel
      .findByIdAndUpdate(userId, { $inc: { followingCount: delta } }, { new: false, session })
      .exec();
  }

  async setFollowersCount(userId: string, followersCount: number, session?: ClientSession): Promise<void> {
    await this.userModel.findByIdAndUpdate(userId, { followersCount }, { new: false, session }).exec();
  }

  async setFollowingCount(userId: string, followingCount: number, session?: ClientSession): Promise<void> {
    await this.userModel.findByIdAndUpdate(userId, { followingCount }, { new: false, session }).exec();
  }

  async setPresence(userId: string, isOnline: boolean, lastSeenAt: Date): Promise<void> {
    await this.userModel
      .findByIdAndUpdate(userId, { isOnline, lastSeenAt }, { new: false })
      .exec();
  }

  async findMany(
    filter: FilterQuery<UserDocument>,
    skip: number,
    limit: number,
    sort: Record<string, 1 | -1> = { createdAt: -1 },
  ): Promise<UserDocument[]> {
    return this.userModel.find(filter).sort(sort).skip(skip).limit(limit).exec();
  }

  async findManyByIds(ids: string[]): Promise<UserDocument[]> {
    if (!ids.length) {
      return [];
    }

    return this.userModel
      .find({ _id: { $in: ids.map((id) => new Types.ObjectId(id)) } })
      .exec();
  }

  async findPrivateUserIds(excludedIds: string[] = []): Promise<string[]> {
    const filter: FilterQuery<UserDocument> = {
      isPrivate: true,
      isDisabled: false,
      ...(excludedIds.length
        ? { _id: { $nin: excludedIds.map((id) => new Types.ObjectId(id)) } }
        : {}),
    };
    const rows = await this.userModel.find(filter).select({ _id: 1 }).lean().exec();
    return rows.map((row) => row._id.toString());
  }

  async findByUsernames(usernames: string[]): Promise<UserDocument[]> {
    if (!usernames.length) {
      return [];
    }

    const normalized = Array.from(new Set(usernames.map((username) => username.toLowerCase())));
    return this.userModel.find({ username: { $in: normalized } }).exec();
  }

  async count(filter: FilterQuery<UserDocument>): Promise<number> {
    return this.userModel.countDocuments(filter).exec();
  }

  async findSuggestionCandidates(
    filter: FilterQuery<UserDocument>,
    limit: number,
  ): Promise<UserDocument[]> {
    return this.userModel
      .find(filter)
      .sort({ isVerified: -1, followersCount: -1, createdAt: -1 })
      .limit(limit)
      .exec();
  }
}
