import models from "$models/index.js";
import { accessGrantHelpers } from "$helpers/index.js";
import mongoose from "mongoose";

const IMMUTABLE_FIELDS = ["_id", "createdAt", "updatedAt", "__v", "createdBy"];

const sanitizeData = (data = {}) => {
  const sanitized = { ...data };

  IMMUTABLE_FIELDS.forEach((field) => delete sanitized[field]);

  if (typeof sanitized.targetRoles === "string") {
    sanitized.targetRoles = sanitized.targetRoles
      .split(",")
      .map((role) => role.trim())
      .filter(Boolean);
  }

  if (!Array.isArray(sanitized.targetRoles)) {
    sanitized.targetRoles = [];
  }

  if (sanitized.audienceType === "all") {
    sanitized.targetRoles = [];
  }

  if (sanitized.image === "") {
    sanitized.image = null;
  } else if (
    sanitized.image &&
    typeof sanitized.image === "object" &&
    sanitized.image._id
  ) {
    sanitized.image = sanitized.image._id;
  }

  if (sanitized.cta && typeof sanitized.cta === "object") {
    sanitized.cta = {
      label:
        typeof sanitized.cta.label === "string" ? sanitized.cta.label.trim() : "",
      url: typeof sanitized.cta.url === "string" ? sanitized.cta.url.trim() : "",
      type: sanitized.cta.type === "external" ? "external" : "internal",
      openInNewTab: Boolean(sanitized.cta.openInNewTab),
    };

    if (!sanitized.cta.label || !sanitized.cta.url) {
      sanitized.cta = {
        label: "",
        url: "",
        type: sanitized.cta.type,
        openInNewTab: sanitized.cta.openInNewTab,
      };
    }
  }

  return sanitized;
};

const populateMessageQuery = (query) =>
  query
    .populate("createdBy", "fullName username role")
    .populate("image", "name type extension createdAt");

const ensureValidImage = async (imageId) => {
  if (!imageId) {
    return null;
  }

  if (!mongoose.Types.ObjectId.isValid(imageId)) {
    const error = new Error("تصویر پیام معتبر نیست.");
    error.status = 422;
    throw error;
  }

  const file = await models.file.findById(imageId).select("_id");
  if (!file) {
    const error = new Error("فایل تصویر پیام یافت نشد.");
    error.status = 404;
    throw error;
  }

  return file;
};

const ensureSingleHomeMessage = async (messageId = null) => {
  const query = { showInHome: true };
  if (messageId) {
    query._id = { $ne: messageId };
  }

  await models.message.updateMany(query, { $set: { showInHome: false } });
};

const hasMessageManagePermission = (user) => {
  if (accessGrantHelpers.hasAllResourceAccess(user, "messages")) {
    return true;
  }

  if (user?.role?.isSuperAdmin || user?.role?.value === "superAdmin") {
    return true;
  }

  const permissions = user?.role?.permissions || [];
  return ["messages:create", "messages:update", "messages:delete"].some((perm) =>
    permissions.includes(perm),
  );
};

const attachReadState = async (messages, userId) => {
  if (!userId || !messages?.length) {
    return messages;
  }

  const reads = await models.messageRead.find({
    user: userId,
    message: { $in: messages.map((item) => item._id) },
  });

  const readMap = new Map(
    reads.map((entry) => [entry.message.toString(), entry.readAt]),
  );

  return messages.map((message) => {
    const data = message.toObject ? message.toObject() : message;
    const readAt = readMap.get(data._id.toString()) || null;

    return {
      ...data,
      isRead: Boolean(readAt),
      readAt,
    };
  });
};

const sortMessagesForViewer = (messages = []) =>
  [...messages].sort((left, right) => {
    if (Boolean(left.isRead) !== Boolean(right.isRead)) {
      return left.isRead ? 1 : -1;
    }

    return new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime();
  });

const buildListQuery = (req) => {
  const query = {};
  const { isActive, audienceType, search } = req.query;

  if (hasMessageManagePermission(req.user)) {
    if (isActive !== undefined) query.isActive = isActive === "true";
    if (audienceType) query.audienceType = audienceType;
  } else {
    query.isActive = true;
    query.$or = [
      { audienceType: "all" },
      {
        audienceType: "roles",
        targetRoles: req.user?.role?.value,
      },
      { createdBy: req.user?._id },
    ];
  }

  if (search) {
    const term = search.toString().trim();
    query.$or = [
      { title: new RegExp(term, "i") },
      { body: new RegExp(term, "i") },
    ];
  }

  return query;
};

const canAccessMessage = (message, user) => {
  if (!message || !user) return false;
  if (hasMessageManagePermission(user)) return true;

  const isTargeted =
    message.audienceType === "all" ||
    (message.audienceType === "roles" &&
      message.targetRoles?.includes(user.role?.value)) ||
    message.createdBy?.toString() === user._id?.toString();

  return message.isActive && isTargeted;
};

export const ALL = async (req, res) => {
  try {
    const pageSize = req.query.pageSize ? parseInt(req.query.pageSize) : 20;
    const currentPage = req.query.page ? parseInt(req.query.page) : 1;
    const skip = pageSize * currentPage - pageSize;
    const query = buildListQuery(req);
    const canManageMessages = hasMessageManagePermission(req.user);

    if (canManageMessages) {
      const messages = await models.message
        .find(query)
        .limit(pageSize)
        .skip(skip)
        .populate("createdBy", "fullName username role")
        .populate("image", "name type extension createdAt")
        .sort({ createdAt: -1 });

      const messagesWithReadState = await attachReadState(
        messages,
        req.user?._id,
      );
      const totalData = await models.message.countDocuments(query);
      const totalPage = pageSize ? Math.ceil(totalData / pageSize) : 0;

      return res.status(200).send({
        messages: messagesWithReadState,
        totalData,
        currentPage,
        totalPage,
        unreadCount: messagesWithReadState.filter((item) => !item.isRead).length,
      });
    }

    const allMessages = await models.message
      .find(query)
      .populate("createdBy", "fullName username role")
      .populate("image", "name type extension createdAt")
      .sort({ createdAt: -1 });

    const messagesWithReadState = await attachReadState(
      allMessages,
      req.user?._id,
    );
    const sortedMessages = sortMessagesForViewer(messagesWithReadState);
    const totalData = sortedMessages.length;
    const totalPage = pageSize ? Math.ceil(totalData / pageSize) : 0;
    const paginatedMessages = sortedMessages.slice(skip, skip + pageSize);
    const unreadCount = sortedMessages.filter((item) => !item.isRead).length;

    res.status(200).send({
      messages: paginatedMessages,
      totalData,
      currentPage,
      totalPage,
      unreadCount,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت پیام‌ها";
    return res.status(status).send({ message });
  }
};

export const PUBLIC_HOME = async (req, res) => {
  try {
    const messageItem = await populateMessageQuery(
      models.message.findOne({
        showInHome: true,
        isActive: true,
      }),
    ).sort({ createdAt: -1 });

    res.status(200).send({ message: messageItem });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت پیام صفحه اصلی";
    return res.status(status).send({ message });
  }
};

export const SINGLE = async (req, res) => {
  try {
    const { id } = req.params;
    const messageItem = await models.message
      .findById(id)
      .populate("createdBy", "fullName username role")
      .populate("image", "name type extension createdAt");

    if (!messageItem) {
      return res.status(404).send({ message: "پیام یافت نشد" });
    }

    if (!canAccessMessage(messageItem, req.user)) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    const [messageWithReadState] = await attachReadState(
      [messageItem],
      req.user?._id,
    );

    res.status(200).send({ message: messageWithReadState });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت پیام";
    return res.status(status).send({ message });
  }
};

export const CREATE = async (req, res) => {
  try {
    const data = sanitizeData(req.body);
    await ensureValidImage(data.image);

    if (data.showInHome) {
      await ensureSingleHomeMessage();
    }

    data.createdBy = req.user._id;

    const messageItem = await models.message.create(data);
    const populated = await models.message
      .findById(messageItem._id)
      .populate("createdBy", "fullName username role")
      .populate("image", "name type extension createdAt");

    res.status(201).send({
      message: "پیام با موفقیت ایجاد شد",
      messages: populated,
    });
  } catch (error) {
    const status = error.name === "ValidationError" ? 400 : error.status || 500;
    const message = error.message || "خطا در ایجاد پیام";
    return res.status(status).send({ message });
  }
};

export const UPDATE = async (req, res) => {
  try {
    const { id } = req.params;
    const updateData = sanitizeData(req.body);
    await ensureValidImage(updateData.image);

    const existingItem = await models.message.findById(id);
    if (!existingItem) {
      return res.status(404).send({ message: "پیام یافت نشد" });
    }

    if (updateData.showInHome) {
      await ensureSingleHomeMessage(id);
    }

    const updated = await models.message
      .findByIdAndUpdate(id, { $set: updateData }, { new: true, runValidators: true })
      .populate("createdBy", "fullName username role")
      .populate("image", "name type extension createdAt");

    res.status(200).send({
      message: "پیام با موفقیت ویرایش شد",
      messages: updated,
    });
  } catch (error) {
    const status = error.name === "ValidationError" ? 400 : error.status || 500;
    const message = error.message || "خطا در ویرایش پیام";
    return res.status(status).send({ message });
  }
};

export const PUBLIC_IMAGE = async (req, res) => {
  try {
    const { fileId } = req.params;

    if (!mongoose.Types.ObjectId.isValid(fileId)) {
      return res.status(404).send({ message: "تصویر یافت نشد" });
    }

    const imageExists = await models.message.exists({ image: fileId });
    if (!imageExists) {
      return res.status(404).send({ message: "تصویر یافت نشد" });
    }

    const file = await models.file.findById(fileId);
    if (!file) {
      return res.status(404).send({ message: "تصویر یافت نشد" });
    }

    res.contentType(file.type || "application/octet-stream");
    return res.send(file.content);
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت تصویر پیام";
    return res.status(status).send({ message });
  }
};

export const DELETE = async (req, res) => {
  try {
    const { id } = req.params;
    const existingItem = await models.message.findById(id);

    if (!existingItem) {
      return res.status(404).send({ message: "پیام یافت نشد" });
    }

    await models.message.findByIdAndDelete(id);

    res.status(200).send({
      message: "پیام با موفقیت حذف شد",
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در حذف پیام";
    return res.status(status).send({ message });
  }
};

export const MARK_READ = async (req, res) => {
  try {
    const { id } = req.params;
    const messageItem = await models.message.findById(id);

    if (!messageItem) {
      return res.status(404).send({ message: "پیام یافت نشد" });
    }

    if (!canAccessMessage(messageItem, req.user)) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    const readEntry = await models.messageRead.findOneAndUpdate(
      { message: id, user: req.user._id },
      { $set: { readAt: new Date() } },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );

    res.status(200).send({
      message: "پیام به عنوان خوانده‌شده ثبت شد",
      readAt: readEntry.readAt,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در ثبت وضعیت پیام";
    return res.status(status).send({ message });
  }
};
