import mongoose from "mongoose";
import asyncErrorHandler from "../Utils/asyncErrorHandler.js";
import constants from "../Utils/constants.js";
import { ChatValidator, MessageSeenValidator, MessageValidator } from "../Utils/Validator/UserValidator.js";
import CustomError from "../Utils/ResponseHandler/CustomError.js";
import User from "../DB/Model/user.model.js";
import Message from "../DB/Model/message.modal.js";
import Chat from "../DB/Model/chat.modal.js";
import logger from "../Config/logger.js";
import FileUpload from "../DB/Model/fileUpload.model.js";
import { populate } from "dotenv";
import push_notification from "../Config/push_notification.js";

/**
 * Fetch All User Chats
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const index = async (user_id) => {
  try {
    const user = await User.findById(user_id);
    if (!user) {
      return { message: "Sender not found", success: false };
    }

    let chatList = [];

    const fetchUserChats = await Chat.find({
      participants: { $all: [user_id] },
    });

    if (fetchUserChats.length > 0) {
      for (let i = 0; i < fetchUserChats.length; i++) {
        let unSeenMessagesCount = 0;

        const latestMessage = await Message.findOne({ chat_id: fetchUserChats[i]._id }).sort({ createdAt: -1 });

        if (latestMessage && latestMessage.sender.toString() !== user._id.toString()) {
          unSeenMessagesCount = await Message.countDocuments({
            chat: fetchUserChats[i]._id,
            read_by: { $nin: [user_id] },
          });
        }

        // Get the receiver ID from participants (only for one-to-one chat)
        const receiverId = fetchUserChats[i].participants.find(
          (participantId) => participantId.toString() !== user_id.toString()
        );

        // Fetch sender and receiver user data (with image populated)
        const [senderUser, receiverUser] = await Promise.all([
          User.findById(user_id)
            .select("_id full_name image")
            .populate("image", "file")
            .lean(),
          receiverId
            ? User.findById(receiverId).select("_id full_name image").populate("image", "file").lean()
            : null,
        ]);

        chatList.push({
          _id: fetchUserChats[i]._id,
          sender: senderUser,
          receiver: receiverUser,
          latestMessage: latestMessage ? latestMessage.content : "",
          lastMessageDateStamp: latestMessage ? latestMessage.createdAt : "",
          unSeenMessageCounter: unSeenMessagesCount,
          // chatType: fetchUserChats[i].participants.length > 2 ? "group" : "private", // 👈 added
          chatType: fetchUserChats[i].chatType,
        });
      }

      // Sort chats by latest message timestamp
      chatList.sort((a, b) => new Date(b.lastMessageDateStamp) - new Date(a.lastMessageDateStamp));
    }

    logger.info(`Get Chats Of User ID ${user._id}`);

    return {
      data: chatList,
      message: "Chat List Fetch Successfully!",
      success: true,
    };
  } catch (error) {
    return { error, message: error.message, success: false };
  }
};

/**
 * Fetch Single Chat by id with Messages
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const chatWithMessages = asyncErrorHandler(async (req, res, next) => {
  const { id } = req.params;
  if (!mongoose.isValidObjectId(id)) {
    return next(CustomError.createError("Invalid ID", constants.BAD_REQUEST));
  }
  const chat = await Chat.findById(id);

  if (!chat) {
    return res.status(constants.NOT_FOUND).json({ message: constants.NO_RECORD_FOUND, success: false });
  }

  const messages = await Message.find({ chat: new mongoose.Types.ObjectId(id) });

  return res.status(constants.OK).json({
    data: { chat, messages },
    message: "Chat Fetch with Messages Successfully!",
    success: true,
  });
});

/**
 * Create Chat (Sender/Receiver)
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const createChat = async (data) => {
  console.log(data, "chat data");

  try {
    // ✅ Case 1: Group Chat
    if (data.chatType === "group") {
      let existingGroupChat = await Chat.findOne({
        _id: new mongoose.Types.ObjectId(data.chat_id),
        chatType: "group",
      }).populate([
        {
          path: "participants",
          select: "full_name image averageRating",
          populate: { path: "image", select: "file" },
        },
        {
          path: "latestMessage",
          populate: {
            path: "sender",
            select: "full_name image averageRating",
            populate: { path: "image", select: "file" },
          },
        },
      ]);

      if (existingGroupChat) {
        return {
          message: "Group chat already exists",
          success: true,
          data: existingGroupChat,
        };
      }

      // 🚀 Otherwise, create new group using participants from this chat_id
      const participantsData = await Chat.findById(data.chat_id)
        .populate("participants")
        .select("participants");

      if (!participantsData) {
        return {
          success: false,
          message: "Base chat not found to create group",
        };
      }

      const participants = participantsData.participants.map((p) => p._id);

      let newGroupChat = await Chat.create({
        participants,
        isGroup: true,
        groupName: data.groupName || "New Group",
        groupImage: data.groupImage || null,
      });

      newGroupChat = await Chat.findById(newGroupChat._id).populate([
        {
          path: "participants",
          select: "full_name image averageRating",
          populate: { path: "image", select: "file" },
        },
        {
          path: "latestMessage",
          populate: {
            path: "sender",
            select: "full_name image averageRating",
            populate: { path: "image", select: "file" },
          },
        },
      ]);

      return {
        message: "Group chat created successfully!",
        success: true,
        data: newGroupChat,
      };
    }

    // ✅ Case 2: One-to-One Chat
    else {
      const { senderId, receiverId } = data;

      console.log(data, "Private Chat Data");

      let existingChat = await Chat.findOne({
        participants: { $all: [senderId, receiverId] },
      }).populate([
        {
          path: "participants",
          select: "full_name image averageRating",
          populate: { path: "image", select: "file" },
        },
        {
          path: "latestMessage",
          populate: {
            path: "sender",
            select: "full_name image averageRating",
            populate: { path: "image", select: "file" },
          },
        },
      ]);

      if (existingChat) {
        return {
          message: "Chat already exists",
          success: true,
          data: existingChat,
        };
      }

      // 🚀 Otherwise create a new one-to-one chat
      let newChat = await Chat.create({
        participants: [data.senderId, data.receiverId],
        chatType: "private",
      });

      newChat = await Chat.findById(newChat._id).populate([
        {
          path: "participants",
          select: "full_name image averageRating",
          populate: { path: "image", select: "file" },
        },
        {
          path: "latestMessage",
          populate: {
            path: "sender",
            select: "full_name image averageRating",
            populate: { path: "image", select: "file" },
          },
        },
      ]);

      return {
        message: "Chat created successfully!",
        success: true,
        data: newChat,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: error.message,
      error,
    };
  }
};

/**
 * Send Message
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const sendMessage = async (data) => {
  try {
    console.log("✅ sendMessage called");

    const { error } = MessageValidator.validate(data);
    if (error)
      return {
        statusCode: constants.UNPROCESSABLE_ENTITY,
        message: error.details[0].message,
        success: false,
      };

    let body = { ...data };

    // ✅ check sender existence
    const user = await User.findById(data.sender);
    if (!user) {
      return { message: `User: ${constants.NO_RECORD_FOUND}`, success: false };
    }

    // ✅ check chat existence
    const chat = await Chat.findById(body.chat_id).populate(
      "participants",
      "_id"
    );
    console.log(chat, "Chat Details");
    if (!chat) {
      return { message: `Chat: ${constants.NO_RECORD_FOUND}`, success: false };
    }

    // ✅ handle private vs group
    if (chat.chatType === "private") {
      if (!data.receiver) {
        return {
          message: "Receiver is required for private chat",
          success: false,
        };
      }
      // always keep array
      body.receiver = [data.receiver];
    } else if (chat.chatType === "group") {
      const groupMembers = chat.participants
        .filter((member) => member._id.toString() !== data.sender.toString())
        .map((member) => member._id);

      body.receiver = groupMembers;
    }

    // ✅ upload media if present
    if (data?.image) {
      const message_image = data.image;
      const uploadedImage = await FileUpload.create({
        file: message_image.filename,
        fileType: message_image.fileType,
        user: user._id,
      });
      body.media = uploadedImage._id;
    }

    // ✅ sender always marks as read
    body.read_by = [data.sender];

    // ✅ save message
    const newMessage = await Message.create(body);

    // ✅ populate message (sender + media + receivers)
    const populatedMessage = await Message.findById(newMessage._id)
      .populate("media")
      .populate({
        path: "sender",
        select: "full_name image averageRating",
        populate: { path: "image", select: "file" }, // image.file
      })
      .populate({
        path: "receiver",
        select: "full_name image averageRating",
        populate: { path: "image", select: "file" }, // image.file
      });

    // ✅ update chat latest message
    await Chat.updateOne(
      { _id: new mongoose.Types.ObjectId(body.chat_id) },
      { latestMessage: newMessage._id }
    );

    // ✅ send push notification to receivers
    if (body.receiver?.length) {
      for (let receiverId of body.receiver) {
        await push_notification(
          "chat_message", // type
          "New Message", // title
          data.content || "You have a new message", // body text
          `/chat/${chat._id}`, // link (optional)
          receiverId // receiver userId
        );
      }
    }

    return {
      message: "Message Sent Successfully!",
      success: true,
      data: populatedMessage,
    };
  } catch (error) {
    console.error("❌ Error in sendMessage:", error);
    return { message: error.message, error, success: false };
  }
};

/**
 * Fetch Messages By ChatId
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const messageSeen = async (data) => {
  try {
    const { error } = MessageSeenValidator.validate(data);
    if (error)
      return {
        statusCode: constants.UNPROCESSABLE_ENTITY,
        message: error.details[0].message,
        success: false,
      };
    if (data.chat_id && mongoose.isValidObjectId(data.read_by)) {
      await Message.updateMany(
        { $and: [{ chat: new mongoose.Types.ObjectId(data.chat_id) }, { read_by: { $nin: [data.read_by] } }] },
        { $push: { read_by: new mongoose.Types.ObjectId(data.read_by) } },
        { new: true }
      );
    } else if (data.message_id && mongoose.isValidObjectId(data.read_by)) {
      await Message.updateOne(
        { $and: [{ _id: new mongoose.Types.ObjectId(data.message_id) }, { read_by: { $nin: [data.read_by] } }] },
        { $push: { read_by: new mongoose.Types.ObjectId(data.read_by) } },
        { new: true }
      );
    }

    return {
      message: "Messages Seen Successfully!",
      success: true,
    };
  } catch (error) {
    return { error, message: error.message, success: false };
  }
};

/**
 * Seen Message of given Chat Id
 *
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const getChatMessages = async (chat_id) => {
  try {
    if (!mongoose.isValidObjectId(chat_id)) {
      return { message: "Invalid Id!", success: false };
    }
    const messages = await Message.find({ chat_id: chat_id }).populate("media").populate({
      path: "sender",
      select: "full_name image averageRating",
      populate: {
        path: "image",
      }
    })
    console.log(messages, "1ty765432345")
    logger.info(`Get Messages Of Chat ID ${chat_id}`);

    return {
      message: "Messages Fetch Successfully!",
      success: true,
      data: messages,
    };
  } catch (error) {
    return { error, message: error.message, success: false };
  }
};

const chatList = async (req, res,) => {
  try {
    const userId = req.user.id;

    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({ message: "User not found", success: false });
    }

    const fetchUserChats = await Chat.find({
      participants: { $all: [userId] },
    });

    if (!fetchUserChats.length) {
      return res.status(200).json({
        data: [],
        message: "No chats found for this user.",
        success: true,
      });
    }

    const chatIds = fetchUserChats.map((chat) => chat._id);

    const latestMessages = await Message.aggregate([
      { $match: { chat: { $in: chatIds } } },
      { $sort: { createdAt: -1 } },
      {
        $group: {
          _id: "$chat",
          messageId: { $first: "$_id" },
          content: { $first: "$content" },
          sender: { $first: "$sender" },
          createdAt: { $first: "$createdAt" },
          read_by: { $first: "$read_by" },
        },
      },
    ]);

    const latestMessageMap = new Map();
    latestMessages.forEach((msg) => {
      latestMessageMap.set(msg._id.toString(), msg);
    });

    const chatList = [];

    for (const chat of fetchUserChats) {
      const chatIdStr = chat._id.toString();
      const latestMessage = latestMessageMap.get(chatIdStr);

      let unreadMessages = 0;
      if (latestMessage && latestMessage.sender.toString() !== userId.toString()) {
        unreadMessages = await Message.countDocuments({
          chat: chat._id,
          read_by: { $nin: [userId] },
        });
      }

      const receiverId = chat.participants.find(
        (participant) => participant.toString() !== userId.toString()
      );
      const [senderUser, receiverUser] = await Promise.all([
        User.findById(userId)
          .select("_id full_name image onlineStatus averageRating")
          .populate("image", "file")
          .lean(),
        User.findById(receiverId)
          .select("_id full_name image onlineStatus averageRating")
          .populate("image", "file")
          .lean(),
      ]);
      chatList.push({
        _id: chat._id,
        sender: senderUser,
        receiver: receiverUser,
        latestMessage: latestMessage
          ? {
            message: latestMessage.content,
            timeAgo: latestMessage.createdAt,
          }
          : null,
        lastMessageDateStamp: latestMessage ? latestMessage.createdAt : null,
        unreadMessages,
      });
    }

    chatList.sort((a, b) => new Date(b.lastMessageDateStamp) - new Date(a.lastMessageDateStamp));

    return res.status(200).json({
      data: chatList,
      message: "Chat List Fetch Successfully!",
      success: true,
    });
  } catch (error) {
    console.error("Error in getChatList:", error);
    return res.status(500).json({
      error,
      message: error.message,
      success: false,
    });
  }
};

const getGroupMembers = async (chat_id) => {

  try {
    if (!mongoose.isValidObjectId(chat_id)) {
      return { message: "Invalid Id!", success: false };
    }
    const chat = await Chat.findById(chat_id).populate("participants");
    if (!chat) {
      return { message: "Chat not found!", success: false };
    }
    return {
      message: "Group members fetched successfully!",
      success: true,
      data: chat.participants,
    };
  } catch (error) {
    return { error, message: error.message, success: false };
  }
};
const ChatController = {
  index,
  createChat,
  sendMessage,
  getChatMessages,
  chatWithMessages,
  messageSeen,
  chatList,
  getGroupMembers
};

export default ChatController;