import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { FilterQuery, Model, Types } from 'mongoose';
import { Notification, NotificationDocument } from './schemas/notification.schema';

@Injectable()
export class NotificationsRepository {
  constructor(
    @InjectModel(Notification.name)
    private readonly notificationModel: Model<NotificationDocument>,
  ) {}

  async create(payload: Partial<Notification>): Promise<NotificationDocument> {
    const notification = await this.notificationModel.create(payload);
    const hydrated = await this.findById(notification.id);
    if (!hydrated) {
      throw new Error('Notification was created but could not be reloaded');
    }
    return hydrated;
  }

  async findById(id: string): Promise<NotificationDocument | null> {
    return this.notificationModel
      .findById(id)
      .populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' })
      .exec();
  }

  async findMine(
    recipientId: string,
    filter: FilterQuery<NotificationDocument>,
    skip: number,
    limit: number,
    sort: Record<string, 1 | -1> = { createdAt: -1 },
  ): Promise<NotificationDocument[]> {
    return this.notificationModel
      .find({
        recipientId: new Types.ObjectId(recipientId),
        ...filter,
      })
      .populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' })
      .sort(sort)
      .skip(skip)
      .limit(limit)
      .exec();
  }

  async findMany(
    filter: FilterQuery<NotificationDocument>,
    skip: number,
    limit: number,
    sort: Record<string, 1 | -1> = { createdAt: -1 },
  ): Promise<NotificationDocument[]> {
    return this.notificationModel
      .find(filter)
      .populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' })
      .sort(sort)
      .skip(skip)
      .limit(limit)
      .exec();
  }

  async countMine(recipientId: string, filter: FilterQuery<NotificationDocument>): Promise<number> {
    return this.notificationModel
      .countDocuments({
        recipientId: new Types.ObjectId(recipientId),
        ...filter,
      })
      .exec();
  }

  async countUnread(recipientId: string): Promise<number> {
    return this.notificationModel
      .countDocuments({
        recipientId: new Types.ObjectId(recipientId),
        read: false,
      })
      .exec();
  }

  async findByEventId(eventId: string): Promise<NotificationDocument | null> {
    return this.notificationModel
      .findOne({ eventId })
      .populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' })
      .exec();
  }

  async countUnreadByFilter(
    recipientId: string,
    filter: FilterQuery<NotificationDocument> = {},
  ): Promise<number> {
    return this.notificationModel
      .countDocuments({
        recipientId: new Types.ObjectId(recipientId),
        read: false,
        ...filter,
      })
      .exec();
  }

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

  async countUnreadAll(filter: FilterQuery<NotificationDocument> = {}): Promise<number> {
    return this.notificationModel
      .countDocuments({
        ...filter,
        read: false,
      })
      .exec();
  }

  async markRead(recipientId: string, notificationId: string): Promise<NotificationDocument | null> {
    const notification = await this.notificationModel
      .findOne({
        _id: new Types.ObjectId(notificationId),
        recipientId: new Types.ObjectId(recipientId),
      })
      .exec();

    if (!notification) {
      return null;
    }

    if (notification.read) {
      await notification.populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' });
      return notification;
    }

    notification.read = true;
    notification.readAt = notification.readAt ?? new Date();
    await notification.save();
    await notification.populate({ path: 'actorId', select: 'name username stageName avatar isVerified isDisabled' });

    return notification;
  }

  async markAllRead(recipientId: string): Promise<number> {
    const result = await this.notificationModel
      .updateMany(
        {
          recipientId: new Types.ObjectId(recipientId),
          read: false,
        },
        {
          read: true,
          readAt: new Date(),
        },
      )
      .exec();

    return result.modifiedCount ?? 0;
  }
}
