import mongoose from "mongoose";
import { Request, Response } from "express";
import NotificationCounter from "../models/NotificationCounter";
import Notification from "../models/Notification";
import User from "../models/User";
import { filterObj } from "../common/helper";

/**
 * Obtiene el contador de notificaciones no leídas del usuario autenticado.
 *
 * Busca el documento `NotificationCounter` asociado al usuario. Si no existe,
 * lo crea con el valor por defecto (0) y lo retorna.
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export async function getNotificationCounter(req: Request, res: Response) {
  const counter = await NotificationCounter.findOne({ user: req.user!.id });
  if (counter) {
    return res.json({ success: true, data: counter.count });
  }

  const cnt = new NotificationCounter({ user: req.user!.id });
  await cnt.save();
  return res.json({ success: true, data: cnt.count });
}

/**
 * Retorna las notificaciones del usuario autenticado con paginación.
 *
 * Utiliza una aggregation pipeline con `$facet` para obtener los datos
 * paginados y el total de registros en una sola consulta. Ordena por
 * fecha de creación descendente.
 *
 * @export
 * @async
 * @param {Request} req - Params: `page`, `size`
 * @param {Response} res
 * @returns {unknown}
 */
export async function getNotifications(req: Request, res: Response) {
  const { page: _page, size: _size } = req.params;
  const page = Number.parseInt(_page, 10);
  const size = Number.parseInt(_size, 10);

  const notifications = await Notification.aggregate([
    { $match: { user: req.user!.id } },
    {
      $facet: {
        data: [
          { $sort: { createdAt: -1, _id: 1 } },
          { $skip: (page - 1) * size },
          { $limit: size },
        ],
        pageInfo: [
          {
            $count: "totalRecords",
          },
        ],
      },
    },
  ]);
  return res.status(200).json({
    success: true,
    message: "Notifications retrieved successfully",
    notifications,
  });
}

/**
 * Marca notificaciones como leídas y actualiza el contador.
 *
 * Recibe un arreglo de `ids`, actualiza `isRead: true` en las notificaciones
 * que pertenecen al usuario autenticado y decrementa el `NotificationCounter`
 * en la cantidad de documentos efectivamente modificados (sin bajar de 0).
 *
 * @export
 * @async
 * @param {Request} req - Body: `ids` (arreglo de ObjectId)
 * @param {Response} res
 * @returns {unknown}
 */
export async function markAsRead(req: Request, res: Response) {
  const { ids }: { ids: string[] } = req.body;
  const _ids = ids.map((id) => new mongoose.Types.ObjectId(id));
  const notifications = await Notification.updateMany(
    { user: req.user!.id, _id: { $in: _ids } },
    { isRead: true },
  );

  const decrementCount = notifications.modifiedCount;
  const notificacionCounter = await NotificationCounter.findOneAndUpdate(
    { user: req.user!.id },
    [
      {
        $set: {
          count: { $max: [{ $subtract: ["$count", decrementCount] }, 0] },
        },
      },
    ],
    { new: true },
  );

  return res.status(200).json({
    success: true,
    message: "Notifications marked as read successfully",
    data: notificacionCounter,
  });
}

/**
 * Marca notificaciones como no leídas y actualiza el contador.
 *
 * Recibe un arreglo de `ids`, actualiza `isRead: false` en las notificaciones
 * que pertenecen al usuario autenticado e incrementa el `NotificationCounter`
 * en la cantidad de documentos efectivamente modificados.
 *
 * @export
 * @async
 * @param {Request} req - Body: `ids` (arreglo de ObjectId)
 * @param {Response} res
 * @returns {unknown}
 */
export async function markAsUnRead(req: Request, res: Response) {
  const { ids }: { ids: string[] } = req.body;
  const _ids = ids.map((id) => new mongoose.Types.ObjectId(id));
  const notifications = await Notification.updateMany(
    { user: req.user!.id, _id: { $in: _ids } },
    { isRead: false },
  );

  const incrementCount = notifications.modifiedCount;
  const notificacionCounter = await NotificationCounter.findOneAndUpdate(
    { user: req.user!.id },
    [{ $inc: { count: incrementCount } }],
    { new: true },
  );

  return res.status(200).json({
    success: true,
    message: "Notifications marked as unread successfully",
    data: notificacionCounter,
  });
}

/**
 * Elimina notificaciones específicas y actualiza el contador.
 *
 * Recibe un arreglo de `ids`, elimina las notificaciones que pertenecen al
 * usuario autenticado y decrementa el `NotificationCounter` en la cantidad
 * de documentos eliminados (sin bajar de 0).
 *
 * @export
 * @async
 * @param {Request} req - Body: `ids` (arreglo de ObjectId)
 * @param {Response} res
 * @returns {unknown}
 */
export async function deleteNotifications(req: Request, res: Response) {
  const { ids }: { ids: string[] } = req.body;
  const _ids = ids.map((id) => new mongoose.Types.ObjectId(id));
  const notifications = await Notification.deleteMany({
    user: req.user!.id,
    _id: { $in: _ids },
  });

  const decrementCount = notifications.deletedCount;
  const notificacionCounter = await NotificationCounter.findOneAndUpdate(
    { user: req.user!.id },
    [
      {
        $set: {
          count: { $max: [{ $subtract: ["$count", decrementCount] }, 0] },
        },
      },
    ],
    { new: true },
  );

  return res.status(200).json({
    success: true,
    message: "Notifications deleted successfully",
    data: notificacionCounter,
  });
}

/**
 * Elimina todas las notificaciones del usuario autenticado.
 *
 * Borra todos los documentos `Notification` asociados al usuario y
 * decrementa el `NotificationCounter` según la cantidad eliminada
 * (sin bajar de 0).
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export async function deleteAllNotifications(req: Request, res: Response) {
  const notifications = await Notification.deleteMany({ user: req.user!.id });

  const decrementCount = notifications.deletedCount;
  const notificacionCounter = await NotificationCounter.findOneAndUpdate(
    { user: req.user!.id },
    [
      {
        $set: {
          count: { $max: [{ $subtract: ["$count", decrementCount] }, 0] },
        },
      },
    ],
    { new: true },
  );

  return res.status(200).json({
    success: true,
    message: "Notifications deleted successfully",
    data: notificacionCounter,
  });
}

/**
 * Actualiza la configuración de notificaciones del usuario.
 *
 * Filtra el body para aceptar únicamente los campos `email`, `sms`, `push`
 * y `promotions`. Si el usuario no tiene configuración previa, inicializa
 * todos los canales en `false` antes de aplicar los cambios.
 *
 * @export
 * @async
 * @param {Request} req - Body: `email?`, `sms?`, `push?`, `promotions?` (booleanos)
 * @param {Response} res
 * @returns {unknown}
 */
export async function editConfiguration(req: Request, res: Response) {
  const { body } = req;
  const user = await User.findById(req.user!.id);
  if (!user) {
    return res
      .status(401)
      .json({ success: false, message: "Unauthorized", code: "unauthorized" });
  }

  const filteredBody = filterObj(body, "email", "sms", "push", "promotions");

  if (!user.notificationsConfig)
    user.notificationsConfig = {
      email: false,
      sms: false,
      push: false,
      promotions: false,
    };
  Object.assign(user.notificationsConfig, filteredBody);
  await user.save();

  return res.status(200).json({
    success: true,
    message: "Configuration updated successfully",
    data: { userId: user._id },
  });
}

/**
 * Obtiene la configuración de notificaciones del usuario.
 *
 * Retorna el objeto `notificationsConfig` del usuario. Si no existe, lo
 * inicializa con todos los canales (`email`, `sms`, `push`, `promotions`)
 * en `false` y persiste antes de responder.
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export async function getConfiguration(req: Request, res: Response) {
  const user = await User.findById(req.user!.id);
  if (!user) {
    return res.status(404).json({
      success: false,
      message: "User not found",
      code: "user_not_found",
    });
  }
  if (!user.notificationsConfig) {
    user.notificationsConfig = {
      email: false,
      sms: false,
      push: false,
      promotions: false,
    };
    await user.save();
  }

  return res.status(200).json({
    success: true,
    message: "Configuration retrieved successfully",
    data: user.notificationsConfig,
  });
}
