import response from "../../response.js";
import models from "../../models/index.js";
import bcrypt from "bcryptjs";
import fs from "fs";
import path from "path";
import {
  clearFileBuffers,
  saveFileToDisk,
} from "../../middlewares/User/uploadUser.js";

export const getUsers = async (req, res) => {
  try {
    const users = await models.User.findAll({
      attributes: {
        exclude: ["PASSWORD"],
        include: [
          [
            models.Sequelize.literal(
              `COALESCE(\`teachers\`.\`NIP\`, \`students\`.\`NISN\`)`
            ),
            "NIP/NISN",
          ],
        ],
      },
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: [],
        },
        {
          model: models.Student,
          as: "students",
          attributes: [],
        },
      ],
    });
    response(200, users, "Success", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Error retrieving users data!", res);
  }
};

export const getAdmins = async (req, res) => {
  try {
    const admins = await models.User.findAll({
      where: {
        ROLE: "admin",
      },
      attributes: {
        exclude: ["PASSWORD"],
      },
    });
    response(200, admins, "Success", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Error retrieving admin data!", res);
  }
};

export const getTeachers = async (req, res) => {
  const { page = 1, limit = 10, search = "", sort = "time" } = req.query;

  try {
    const totalTeachersCount = await models.User.count({
      where: {
        ROLE: "teacher",
      },
    });

    const { count, rows: teachers } = await models.User.findAndCountAll({
      where: {
        ROLE: "teacher",
        ...(search && {
          [models.Op.or]: [
            {
              NAME_USERS: {
                [models.Op.like]: `%${search}%`,
              },
            },
            {
              EMAIL: {
                [models.Op.like]: `%${search}%`,
              },
            },
            {
              "$teachers.NIP$": {
                [models.Op.like]: `%${search}%`,
              },
            },
          ],
        }),
      },
      attributes: ["ID", "NAME_USERS", "EMAIL", "ROLE", "TIME_USERS"],
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
      ],
      distinct: true,
      raw: true,
      nest: true,
    });

    const formattedTeachers = teachers.map((teacher) => ({
      ID: teacher.ID,
      NAME_USERS: teacher.NAME_USERS,
      EMAIL: teacher.EMAIL,
      NIP: teacher.teachers.NIP,
      ROLE: teacher.ROLE,
      TIME_USERS: teacher.TIME_USERS,
    }));

    if (sort === "nip") {
      formattedTeachers.sort((a, b) => a.NIP.localeCompare(b.NIP));
    } else if (sort === "name") {
      formattedTeachers.sort((a, b) =>
        a.NAME_USERS.localeCompare(b.NAME_USERS)
      );
    } else if (sort === "email") {
      formattedTeachers.sort((a, b) => a.EMAIL.localeCompare(b.EMAIL));
    } else {
      formattedTeachers.sort(
        (a, b) => new Date(b.TIME_USERS) - new Date(a.TIME_USERS)
      );
    }

    const paginatedTeachers = formattedTeachers.slice(
      (page - 1) * limit,
      page * limit
    );

    const totalPages = Math.ceil(count / limit);
    const currentPage = parseInt(page);

    response(
      200,
      {
        teachers: paginatedTeachers,
        currentPage,
        totalPages,
        totalSearchedItems: count,
        totalTeachers: totalTeachersCount,
      },
      "Teachers retrieved successfully",
      res
    );
  } catch (error) {
    console.log(error);
    response(500, null, "Error retrieving teacher data!", res);
  }
};

export const getStudents = async (req, res) => {
  const { page = 1, limit = 10, search = "", sort = "time" } = req.query;

  try {
    const totalStudentsCount = await models.User.count({
      where: {
        ROLE: "student",
      },
    });

    const { count, rows: students } = await models.User.findAndCountAll({
      where: {
        ROLE: "student",
        ...(search && {
          [models.Op.or]: [
            {
              NAME_USERS: {
                [models.Op.like]: `%${search}%`,
              },
            },
            {
              EMAIL: {
                [models.Op.like]: `%${search}%`,
              },
            },
            {
              "$students.NISN$": {
                [models.Op.like]: `%${search}%`,
              },
            },
          ],
        }),
      },
      attributes: ["ID", "NAME_USERS", "EMAIL", "ROLE", "TIME_USERS"],
      include: [
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
          include: [
            {
              model: models.Class,
              as: "studentClass",
              attributes: ["NAME_CLASS"],
            },
          ],
        },
      ],
      distinct: true,
      raw: true,
      nest: true,
    });

    const formattedStudents = students.map((student) => ({
      ID: student.ID,
      NISN: student.students.NISN,
      NAME_USERS: student.NAME_USERS,
      EMAIL: student.EMAIL,
      NAME_CLASS: student.students.studentClass.NAME_CLASS,
      ROLE: student.ROLE,
      TIME_USERS: student.TIME_USERS,
    }));

    if (sort === "nisn") {
      formattedStudents.sort((a, b) =>
        String(a.NISN).localeCompare(String(b.NISN))
      );
    } else if (sort === "name") {
      formattedStudents.sort((a, b) =>
        a.NAME_USERS.localeCompare(b.NAME_USERS)
      );
    } else if (sort === "email") {
      formattedStudents.sort((a, b) => a.EMAIL.localeCompare(b.EMAIL));
    } else {
      formattedStudents.sort(
        (a, b) => new Date(b.TIME_USERS) - new Date(a.TIME_USERS)
      );
    }

    const paginatedStudents = formattedStudents.slice(
      (page - 1) * limit,
      page * limit
    );

    const totalPages = Math.ceil(count / limit);
    const currentPage = parseInt(page);

    response(
      200,
      {
        students: paginatedStudents,
        currentPage,
        totalPages,
        totalSearchedItems: count,
        totalStudents: totalStudentsCount,
      },
      "Students retrieved successfully",
      res
    );
  } catch (error) {
    console.log(error);
    response(500, null, "Error retrieving students data!", res);
  }
};

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

    const userWithDetails = await models.User.findByPk(id, {
      attributes: {
        exclude: ["PASSWORD"],
      },
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
          include: [
            {
              model: models.Class,
              as: "studentClass",
              attributes: ["NAME_CLASS"],
            },
          ],
        },
      ],
    });

    if (!userWithDetails) {
      return response(404, null, "User not found", res);
    }

    let additionalField = null;
    if (userWithDetails.ROLE === "teacher") {
      additionalField = { NIP: userWithDetails.teachers.NIP };
    } else if (userWithDetails.ROLE === "student") {
      additionalField = {
        NISN: userWithDetails.students.NISN,
        NAME_CLASS: userWithDetails.students.studentClass
          ? userWithDetails.students.studentClass.NAME_CLASS
          : null,
      };
    }

    const responseObject = {
      ID: userWithDetails.ID,
      NAME_USERS: userWithDetails.NAME_USERS,
      EMAIL: userWithDetails.EMAIL,
      ROLE: userWithDetails.ROLE,
      ...additionalField,
      PICTURE: userWithDetails.PICTURE,
    };

    response(200, responseObject, "Success", res);
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving user data!", res);
  }
};

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

    const userWithDetails = await models.User.findOne({
      where: { NAME_USERS: name },
      attributes: {
        exclude: ["PASSWORD"],
      },
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
          include: [
            {
              model: models.Class,
              as: "studentClass",
              attributes: ["NAME_CLASS"],
            },
          ],
        },
      ],
    });

    if (!userWithDetails) {
      return response(404, null, "User not found", res);
    }

    let additionalField = null;
    if (userWithDetails.ROLE === "teacher") {
      additionalField = { NIP: userWithDetails.teachers.NIP };
    } else if (userWithDetails.ROLE === "student") {
      additionalField = {
        NISN: userWithDetails.students.NISN,
        NAME_CLASS: userWithDetails.students.studentClass
          ? userWithDetails.students.studentClass.NAME_CLASS
          : null,
      };
    }

    const responseObject = {
      ID: userWithDetails.ID,
      NAME_USERS: userWithDetails.NAME_USERS,
      EMAIL: userWithDetails.EMAIL,
      ROLE: userWithDetails.ROLE,
      ...additionalField,
      PICTURE: userWithDetails.PICTURE,
    };

    response(200, responseObject, "Success", res);
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving user data!", res);
  }
};

export const updateUserById = async (req, res) => {
  const transaction = await models.db.transaction();

  const { PICTURE } = req.filesToSave || {};

  try {
    const { id } = req.params;
    const { NAME_USERS, EMAIL, NIP, NISN } = req.body;

    const user = await models.User.findByPk(id, {
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
        },
      ],
      transaction,
    });

    if (!user) {
      clearFileBuffers({ PICTURE });
      await transaction.rollback();
      return response(404, null, "User not found", res);
    }

    if (user.ROLE === "teacher" && NISN) {
      clearFileBuffers({ PICTURE });
      await transaction.rollback();
      return response(400, null, "Role is teacher, but NISN is provided", res);
    }
    if (user.ROLE === "student" && NIP) {
      clearFileBuffers({ PICTURE });
      await transaction.rollback();
      return response(400, null, "Role is student, but NIP is provided", res);
    }

    if (EMAIL && EMAIL !== user.EMAIL) {
      const emailExists = await models.User.findOne({
        where: { EMAIL: EMAIL },
        transaction,
      });
      if (emailExists) {
        clearFileBuffers({ PICTURE });
        await transaction.rollback();
        return response(400, null, "Email already in use", res);
      }
      user.EMAIL = EMAIL;
    }

    user.NAME_USERS = NAME_USERS || user.NAME_USERS;

    if (user.ROLE === "teacher" && NIP) {
      let teacher = await models.Teacher.findOne({
        where: { ID: id },
        transaction,
      });
      if (teacher) {
        teacher.NIP = NIP;
        await teacher.save({ transaction });
      } else {
        teacher = await models.Teacher.create(
          { ID: id, NIP: NIP },
          { transaction }
        );
      }
    }

    if (user.ROLE === "student" && NISN) {
      let student = await models.Student.findOne({
        where: { ID: id },
        transaction,
      });
      if (student) {
        student.NISN = NISN;
        await student.save({ transaction });
      } else {
        student = await models.Student.create(
          { ID: id, NISN: NISN },
          { transaction }
        );
      }
    }

    if (PICTURE) {
      if (user.PICTURE) {
        const oldPicturePath = path.join("public/uploads/avatar", user.PICTURE);
        if (fs.existsSync(oldPicturePath)) {
          fs.unlinkSync(oldPicturePath);
        }
      }
      user.PICTURE = saveFileToDisk(PICTURE, user.ID, user.NAME_USERS);
    }

    await user.save({ transaction });

    await user.reload({
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
        },
      ],
      transaction,
    });

    await transaction.commit();

    let userResponse = {
      ID: user.ID,
      NAME_USERS: user.NAME_USERS,
      EMAIL: user.EMAIL,
      ROLE: user.ROLE,
      PICTURE: user.PICTURE,
    };

    if (user.ROLE === "student" && user.students) {
      userResponse.NISN = user.students.NISN;
    } else if (user.ROLE === "teacher" && user.teachers) {
      userResponse.NIP = user.teachers.NIP;
    }

    return response(200, userResponse, "User updated successfully", res);
  } catch (error) {
    clearFileBuffers({ PICTURE });
    await transaction.rollback();
    console.log(error);
    return response(500, null, "Internal Server Error", res);
  }
};

export const updateUserPasswordById = async (req, res) => {
  try {
    const { id } = req.params;
    const { OLD_PASSWORD, PASSWORD, CONFIRM_PASSWORD } = req.body;

    if (!OLD_PASSWORD || !PASSWORD || !CONFIRM_PASSWORD) {
      return response(400, null, "All fields must be filled.", res);
    }

    if (PASSWORD !== CONFIRM_PASSWORD) {
      return response(
        400,
        null,
        "New password and confirm password do not match.",
        res
      );
    }

    const user = await models.User.findByPk(id);
    if (!user) {
      return response(404, null, "User not found.", res);
    }

    const isMatch = await bcrypt.compare(OLD_PASSWORD, user.PASSWORD);
    if (!isMatch) {
      return response(400, null, "Incorrect old password.", res);
    }

    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(PASSWORD, salt);

    user.PASSWORD = hashedPassword;
    await user.save();

    response(200, null, "Password updated successfully.", res);
  } catch (error) {
    response(500, null, "Internal Server Error", res);
  }
};

export const deleteUserById = async (req, res) => {
  const transaction = await models.db.transaction();

  try {
    const { id } = req.params;

    const user = await models.User.findByPk(id, { transaction });

    if (!user) {
      await transaction.rollback();
      return response(404, null, "User not found", res);
    }

    const studentLearnings = await models.StdLearning.findAll({
      where: { ID: id },
      attributes: ["ID_STUDENT_LEARNING"],
      transaction,
    });

    const studentLearningIds = studentLearnings.map(
      (sl) => sl.ID_STUDENT_LEARNING
    );

    if (studentLearningIds.length > 0) {
      await models.Monitoring.destroy({
        where: {
          ID_STUDENT_LEARNING: studentLearningIds,
        },
        transaction,
      });

      await models.StdExercise.destroy({
        where: {
          ID_STUDENT_LEARNING: studentLearningIds,
        },
        transaction,
      });
    }

    await models.StdLearning.destroy({
      where: { ID: id },
      transaction,
    });

    await models.Report.destroy({
      where: { ID: id },
      transaction,
    });

    if (user.ROLE === "teacher") {
      const teacher = await models.Teacher.findOne({
        where: { ID: id },
        attributes: ["ID_GURU"],
        transaction,
      });

      if (teacher) {
        await models.Monitoring.update(
          { ID_GURU: null },
          {
            where: { ID_GURU: teacher.ID_GURU },
            transaction,
          }
        );

        await teacher.destroy({ transaction });
      }
      await models.Teacher.destroy({ where: { ID: id }, transaction });
    } else if (user.ROLE === "student") {
      await models.Student.destroy({ where: { ID: id }, transaction });
    }

    await user.destroy({ transaction });

    await transaction.commit();
    return response(200, null, "User deleted successfully", res);
  } catch (error) {
    await transaction.rollback();
    console.log(error);
    return response(500, null, "Internal Server Error", res);
  }
};

export const getMe = async (req, res) => {
  try {
    const user = req.user;

    const userWithDetails = await models.User.findByPk(user.ID, {
      attributes: {
        exclude: ["PASSWORD"],
      },
      include: [
        {
          model: models.Teacher,
          as: "teachers",
          attributes: ["NIP"],
        },
        {
          model: models.Student,
          as: "students",
          attributes: ["NISN"],
          include: [
            {
              model: models.Class,
              as: "studentClass",
              attributes: ["NAME_CLASS"],
            },
          ],
        },
      ],
    });

    if (!userWithDetails) {
      return response(404, null, "User not found", res);
    }

    let additionalField = null;
    if (userWithDetails.ROLE === "teacher") {
      additionalField = { NIP: userWithDetails.teachers.NIP };
    } else if (userWithDetails.ROLE === "student") {
      additionalField = {
        NISN: userWithDetails.students.NISN,
        NAME_CLASS: userWithDetails.students.studentClass
          ? userWithDetails.students.studentClass.NAME_CLASS
          : null,
      };
    }

    const responseObject = {
      ID: userWithDetails.ID,
      NAME_USERS: userWithDetails.NAME_USERS,
      EMAIL: userWithDetails.EMAIL,
      ROLE: userWithDetails.ROLE,
      ...additionalField,
      PICTURE: userWithDetails.PICTURE,
    };

    response(200, responseObject, "Success", res);
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving user data!", res);
  }
};
