import Notification, { NotificationType } from "../models/notification";
import { sendPushNotification } from "./pushNotification.service";
import User from "../models/users";
import Attendance from "../models/attendance";
import QuizResult from "../models/quizResult";
import { emitUserNotification } from "../utils/socket";

// ...existing code...
// Notify all users when a new quiz is published
export const notifyQuizPublished = async (quizId: string, title: string) => {
  await createBroadcastNotifications({
    title: "New quiz published",
    message: `${title} is now available.`,
    type: "QUIZ_PUBLISHED",
    eventKey: `QUIZ_PUBLISHED:${quizId}`,
    meta: { quizId },
  });
};
const badRequest = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 400 });

const notFound = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 404 });

const toDateStr = (d: Date): string =>
  `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;

interface CreateNotificationInput {
  userId: string;
  title: string;
  message: string;
  type: NotificationType;
  eventKey?: string;
  meta?: Record<string, unknown>;
}

export const createNotification = async ({
  userId,
  title,
  message,
  type,
  eventKey,
  meta,
}: CreateNotificationInput) => {
  try {
    const created = await Notification.create({
      user: userId,
      title,
      message,
      type,
      eventKey,
      meta,
    });

    const payload = {
      _id: created._id,
      title: created.title,
      message: created.message,
      type: created.type,
      isRead: created.isRead,
      meta: created.meta,
      createdAt: created.createdAt,
    };

    emitUserNotification(userId, payload);

    // Fire and forget push notification
    (async () => {
      try {
        const data: Record<string, string> = {};
        if (created.meta && typeof created.meta === "object") {
          for (const [k, v] of Object.entries(created.meta)) {
            if (typeof v === "string") data[k] = v;
          }
        }
        await sendPushNotification(
          userId,
          created.title,
          created.message,
          created.type,
          data,
        );
      } catch (pushErr) {
        // Optionally log pushErr
      }
    })();

    return payload;
  } catch (error) {
    const e = error as { code?: number };
    if (e.code === 11000 && eventKey) {
      const existing = await Notification.findOne({ user: userId, eventKey })
        .select("_id title message type isRead meta createdAt")
        .lean();
      return existing;
    }
    throw error;
  }
};

export const createBroadcastNotifications = async (
  input: Omit<CreateNotificationInput, "userId">,
) => {
  const users = await User.find({ role: "user", status: "active" })
    .select("_id")
    .lean();
  if (users.length === 0) return 0;

  await Promise.all(
    users.map((u) =>
      createNotification({
        userId: u._id.toString(),
        title: input.title,
        message: input.message,
        type: input.type,
        eventKey: input.eventKey
          ? `${input.eventKey}:${u._id.toString()}`
          : undefined,
      }),
    ),
  );

  return users.length;
};

export const getUserNotifications = async (
  userId: string,
  page = 1,
  limit = 10,
) => {
  if (!Number.isInteger(page) || page < 1)
    throw badRequest("Invalid page. page must be an integer >= 1");
  if (!Number.isInteger(limit) || limit < 1 || limit > 50)
    throw badRequest(
      "Invalid limit. limit must be an integer between 1 and 50",
    );

  const skip = (page - 1) * limit;

  const [items, total, unreadCount] = await Promise.all([
    Notification.find({ user: userId })
      .select("title message type isRead meta createdAt")
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    Notification.countDocuments({ user: userId }),
    Notification.countDocuments({ user: userId, isRead: false }),
  ]);

  const totalPages = Math.ceil(total / limit);

  return {
    notifications: items,
    unreadCount,
    pagination: {
      total,
      page,
      limit,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
    },
  };
};

export const markNotificationRead = async (
  userId: string,
  notificationId: string,
) => {
  const notification = await Notification.findOneAndUpdate(
    { _id: notificationId, user: userId },
    { isRead: true },
    { new: true },
  )
    .select("title message type isRead meta createdAt")
    .lean();

  if (!notification) throw notFound("Notification not found");
  return notification;
};

export const markAllNotificationsRead = async (userId: string) => {
  const result = await Notification.updateMany(
    { user: userId, isRead: false },
    { isRead: true },
  );
  return { modifiedCount: result.modifiedCount };
};

const getUnsubmittedActiveUserIds = async (quizId: string) => {
  const [activeUsers, submissions] = await Promise.all([
    User.find({ role: "user", status: "active" }).select("_id").lean(),
    QuizResult.find({ quiz: quizId }).select("user").lean(),
  ]);

  const submitted = new Set(submissions.map((s) => s.user.toString()));
  return activeUsers
    .map((u) => u._id.toString())
    .filter((id) => !submitted.has(id));
};

export const notifyQuizStartReminder = async (
  quizId: string,
  title: string,
  startDate: Date,
) => {
  const targets = await getUnsubmittedActiveUserIds(quizId);
  await Promise.all(
    targets.map((userId) =>
      createNotification({
        userId,
        title: "Quiz starts soon",
        message: `${title} starts in 15 minutes.`,
        type: "QUIZ_REMINDER",
        eventKey: `QUIZ_REMINDER:${quizId}:${startDate.toISOString()}`,
        meta: { quizId, startDate },
      }),
    ),
  );
};

export const notifyQuizEndingSoon = async (
  quizId: string,
  title: string,
  endDate: Date,
) => {
  const targets = await getUnsubmittedActiveUserIds(quizId);
  await Promise.all(
    targets.map((userId) =>
      createNotification({
        userId,
        title: "Quiz ending soon",
        message: `${title} ends in 1 hour.`,
        type: "QUIZ_ENDING_SOON",
        eventKey: `QUIZ_ENDING_SOON:${quizId}:${endDate.toISOString()}`,
        meta: { quizId, endDate },
      }),
    ),
  );
};

export const notifyQuizResult = async (
  userId: string,
  quizId: string,
  title: string,
  score: number,
  totalPoints: number,
) => {
  await createNotification({
    userId,
    title: "Quiz result available",
    message: `You scored ${score}/${totalPoints} in ${title}.`,
    type: "QUIZ_RESULT",
    eventKey: `QUIZ_RESULT:${quizId}:${userId}`,
    meta: { quizId, score, totalPoints },
  });
};

export const notifyAttendanceStreak = async (
  userId: string,
  currentStreak: number,
  warning?: string | null,
) => {
  const message =
    warning ||
    `Great job! Your attendance streak is now ${currentStreak} day(s).`;
  await createNotification({
    userId,
    title: "Attendance streak update",
    message,
    type: "ATTENDANCE_STREAK",
    eventKey: `ATTENDANCE_STREAK:${userId}:${toDateStr(new Date())}:${currentStreak}`,
    meta: { currentStreak },
  });
};

export const sendDailyAttendanceReminder = async () => {
  const today = toDateStr(new Date());

  const [activeUsers, todaysAttendance] = await Promise.all([
    User.find({ role: "user", status: "active" }).select("_id").lean(),
    Attendance.find({ date: today }).select("user").lean(),
  ]);

  const checkedInSet = new Set(todaysAttendance.map((a) => a.user.toString()));
  const targets = activeUsers.filter(
    (u) => !checkedInSet.has(u._id.toString()),
  );

  await Promise.all(
    targets.map((u) =>
      createNotification({
        userId: u._id.toString(),
        title: "Attendance reminder",
        message: "Please remember to check in today.",
        type: "ATTENDANCE_REMINDER",
        eventKey: `DAILY_ATTENDANCE_REMINDER:${u._id.toString()}:${today}`,
      }),
    ),
  );

  return targets.length;
};
