const {
  insertMessage,
  getConversationsForUser,
  deleteContact,
  deleteMessage,
  removeUserFromGroupById,
  isConversationMember,
  isAdmin,
  addAdministrator,
  removeAdministrator,
} = require("../db/db");
const { isValidUsername } = require("../utils/filter");
const { verifyJwtToken } = require("../auth/jwt");

function initializeSocket(io) {
  io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    if (!token) {
      console.log("(socket) Not logged in");
      return next(new Error("Not logged in"));
    }

    try {
      const { username, user_id } = verifyJwtToken(token);
      if (!username || !user_id) {
        console.log("(socket) Invalid token payload");
        return next(new Error("(socket) Invalid token payload"));
      }

      socket.username = username;

      if (!isValidUsername(username)) {
        console.log("(socket) Invalid username");
        return;
      }

      socket.user_id = user_id;
      console.log(
        `(socket) socket id: ${socket.id}, username: ${username}, user_id: ${user_id}`,
      );
      next();
    } catch (error) {
      console.error("(socket) Token verification failed:", error.message);
      next(new Error("(socket) Invalid token"));
    }
  });

  io.on("connection", async (socket) => {
    const username = socket.username;
    if (!username) {
      socket.on("disconnect", () => {
        console.log(
          "(socket)",
          socket.id,
          " disconnected due to: invalid username/token",
        );
      });
      return;
    }

    if (!isValidUsername(username)) {
      socket.on("disconnect", () => {
        console.log(
          "(socket)",
          socket.id,
          " disconnected due to: invalid username/token",
        );
      });
      return;
    }

    let conversations = [];
    try {
      conversations = await getConversationsForUser(socket.user_id);
      conversations.push(socket.user_id);
      socket.join(conversations);
      console.log(`User: ${socket.username} joined to: ${conversations}`);
    } catch (e) {
      console.error("(socket) Failed to get user conversations");
    }

    socket.on("join room", async (conversation_id, callback) => {
      const isMember = await isConversationMember(
        socket.user_id,
        conversation_id,
      );
      if (isMember) {
        console.log("Join room for: ", username, "room: ", conversation_id);
        socket.join(conversation_id);
        return callback({
          status: "ok",
          message: "Successfully joined to conversation",
        });
      } else {
        return callback({
          status: "error",
          message: "You are not member of this group",
        });
      }
    });

    socket.on("chat message", async (msg, callback) => {
      const { message, recipient, recipient_id, attachment_urls } = msg;
      const sender = socket.username;

      if (!message && !attachment_urls) {
        callback({
          status: "error",
          message: "No message or attachment provided",
        });
        return;
      }
      if (!recipient) {
        callback({
          status: "error",
          message: "No recipient provided",
        });
        return;
      }

      try {
        const insertedMessage = await insertMessage(
          socket.user_id,
          recipient,
          message,
          attachment_urls,
        );
        if (!insertedMessage) {
          callback({
            status: "error",
            message: "Failed to insert message",
          });
          return;
        } else if (insertedMessage.length === 0) {
          callback({
            status: "error",
            message: "You are not an member of this conversation",
          });
        }
        const { message_id, content, sent_at, sender_id, conversation_id } =
          insertedMessage;
        console.log("(socket) received from chat message", msg);

        io.to(recipient).to(recipient_id).emit("chat message", {
          sender,
          message: content,
          attachment_urls,
          recipient,
          message_id,
          sender_id,
          sent_at,
          conversation_id,
        });
        console.log(
          `(socket) sent on 'chat message' socket to: recipient: ${recipient}, recipient_id: ${recipient_id} `,
          {
            sender,
            message,
            attachment_urls,
            recipient,
            sent_at,
            message_id,
            sender_id,
          },
        );

        callback({
          message_id,
          status: "ok",
          message: "Received message",
        });
      } catch (e) {
        console.error("(socket) Failed to insert message ", e);
        callback({
          status: "error",
          message: "Internal server error",
        });
      }
    });

    socket.on("delete message", async (msg, callback) => {
      const { conversation_id, message_id } = msg;
      if (!message_id) {
        return callback({ status: "error", message: "No message id provided" });
      }
      if (!conversation_id) {
        return callback({
          status: "error",
          message: "No conversation id provided",
        });
      }

      const result = await deleteMessage(
        socket.user_id,
        conversation_id,
        message_id,
      );
      if (result?.message !== undefined) {
        return callback({ status: "error", message: result.message });
      } else {
        io.to(conversation_id).emit("delete message", {
          conversation_id,
          message_id,
        });
        return callback({
          status: "ok",
          message: "Successfully deleted message",
        });
      }
    });

    socket.on("delete contact", async (conversation_id, callback) => {
      console.log(
        "(socket) delete contact, conversation_id: ",
        conversation_id,
      );
      const user_id = socket.user_id;
      if (!conversation_id) {
        callback({ status: "error", message: "No conversation id provided" });
      }
      const result = await deleteContact(user_id, conversation_id);

      if (result?.message) {
        callback({ status: "error", message: result.message });
      }
      callback({ status: "ok", message: "Successfully deleted contact" });
      io.to(conversation_id).emit("left group", { conversation_id, user_id });
    });

    socket.on("remove user from group", async (msg, callback) => {
      const { group_id, user_id } = msg;

      if (!group_id) {
        return callback({ status: "error", message: "No group id provided" });
      }
      if (!user_id) {
        return callback({ status: "error", message: "No user id provided" });
      }

      try {
        const result = await removeUserFromGroupById(group_id, user_id);

        if (result?.message) {
          return callback({ status: "error", message: result.message });
        }

        // Get all sockets in the room
        const socketsInRoom = await io.in(group_id).fetchSockets();
        // Remove user from room
        for (const socketInstance of socketsInRoom) {
          if (socketInstance.user_id === user_id) {
            socketInstance.leave(group_id);
          }
        }

        io.to(group_id).to(user_id).emit("left group", {
          group_id,
          user_id,
        });
        console.log(
          `Successfully removed user: ${user_id}, from group: ${group_id}`,
        );
        return callback({
          status: "ok",
          message: "Successfully removed user from group",
        });
      } catch (error) {
        console.error("Failed to remove user from group:", error);
        return callback({
          status: "error",
          message: "Internal server error",
        });
      }
    });

    socket.on("added administrator", async (msg, callback) => {
      const { group_id, user_id } = msg;
      if (!group_id) {
        return callback({
          status: "error",
          message: "No conversation id provided",
        });
      }
      if (!user_id) {
        return callback({ status: "error", message: "No user id provided" });
      }
      const isUserAdmin = await isAdmin(socket.user_id, group_id);
      if (!isUserAdmin) {
        return callback({
          status: "error",
          message: "You is not an administrator",
        });
      }
      const result = await addAdministrator(group_id, user_id, socket.user_id);
      if (result?.message) {
        return callback({ status: "error", message: result.message });
      }
      console.log(
        `Successfully added user: ${user_id} to administrators to group: ${group_id}`,
      );
      io.to(group_id).emit("added administrator", {
        group_id,
        user_id,
      });

      return callback({
        status: "ok",
        message: "Successfully added administrator",
      });
    });

    socket.on("removed administrator", async (msg, callback) => {
      const { group_id, user_id } = msg;
      if (!group_id) {
        return callback({
          status: "error",
          message: "No conversation id provided",
        });
      }
      if (!user_id) {
        return callback({ status: "error", message: "No user id provided" });
      }

      const result = await removeAdministrator(
        group_id,
        user_id,
        socket.user_id,
      );
      if (result?.message) {
        return callback({ status: "error", message: result.message });
      }
      console.log(
        `Successfully removed user: ${user_id} from administrators in group: ${group_id}`,
      );
      io.to(group_id).emit("removed administrator", {
        group_id,
        user_id,
      });
      return callback({
        status: "ok",
        message: "Successfully removed administrator",
      });
    });

    socket.on("disconnect", (reason) => {
      console.log("(socket)", socket.id, " disconnected due to: ", reason);
    });
  });
}

module.exports = { initializeSocket };
