import response from "../../response.js";
import models from "../../models/index.js";
import { createObjectCsvWriter } from "csv-writer";
import { Readable } from "stream";
import fs from "fs";
import os from "os";
import path from "path";

export const getMonitorings = async (req, res) => {
  try {
    const monitoring = await models.Monitoring.findAll();
    response(200, monitoring, "Success", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Error retrieving monitoring data!", res);
  }
};

export const getMonitoringById = async (req, res) => {
  try {
    const { id } = req.params;
    const monitoring = await models.Monitoring.findByPk(id);

    if (!monitoring) {
      return response(404, null, "Monitoring data not found", res);
    }

    response(200, monitoring, "Success", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Internal Server Error", res);
  }
};

export const createMonitoring = async (req) => {
  const { ID_STUDENT_LEARNING } = req.body;

  if (!req.user) {
    throw new Error("User not authenticated");
  }

  if (!ID_STUDENT_LEARNING) {
    throw new Error("Student Learning ID is required");
  }

  try {
    const existingMonitoring = await models.Monitoring.findOne({
      where: { ID_STUDENT_LEARNING: ID_STUDENT_LEARNING },
    });

    if (existingMonitoring) {
      return existingMonitoring.toJSON();
    }

    const stdLearning = await models.StdLearning.findByPk(ID_STUDENT_LEARNING);

    if (!stdLearning) {
      throw new Error("Student learning data not found");
    }

    const userID = stdLearning.ID;

    const student = await models.Student.findOne({ where: { ID: userID } });

    if (!student) {
      throw new Error("Student data not found");
    }

    const { ID_SISWA } = student;

    const studentClass = await models.Student.findOne({
      where: { ID_SISWA: ID_SISWA },
      attributes: ["ID_CLASS"],
    });

    const ID_CLASS =
      studentClass && studentClass.ID_CLASS ? studentClass.ID_CLASS : null;

    const newMonitoring = await models.Monitoring.create({
      ID_STUDENT_LEARNING,
      ID_CLASS,
    });

    return newMonitoring.toJSON();
  } catch (error) {
    console.error(error);
    throw new Error("Internal Server Error");
  }
};

export const updateMonitoringClass = async ({
  ID_STUDENT_LEARNING,
  ID_CLASS,
}) => {
  if (!ID_STUDENT_LEARNING || !ID_CLASS) {
    throw new Error("Student Learning ID and Class ID are required");
  }

  try {
    const monitoring = await models.Monitoring.findOne({
      where: { ID_STUDENT_LEARNING },
    });

    if (!monitoring) {
      throw new Error("Monitoring data not found");
    }

    monitoring.ID_CLASS = ID_CLASS;
    await monitoring.save();

    return monitoring;
  } catch (error) {
    console.error(error);
    throw error;
  }
};

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

  try {
    const { count, rows: monitorings } =
      await models.Monitoring.findAndCountAll({
        include: [
          {
            model: models.StdLearning,
            as: "stdLearningMonitoring",
            include: [
              {
                model: models.User,
                as: "learningUser",
                attributes: ["NAME_USERS"],
                include: [
                  {
                    model: models.Student,
                    as: "students",
                    attributes: ["NISN"],
                    include: [
                      {
                        model: models.Class,
                        as: "studentClass",
                        attributes: ["NAME_CLASS"],
                      },
                    ],
                  },
                ],
              },
              {
                model: models.Level,
                as: "level",
                attributes: ["NAME_LEVEL"],
                include: [
                  {
                    model: models.Topic,
                    as: "levelTopic",
                    attributes: ["NAME_TOPIC"],
                    include: [
                      {
                        model: models.Section,
                        as: "topicSection",
                        attributes: ["NAME_SECTION"],
                      },
                    ],
                  },
                ],
              },
            ],
          },
        ],
        where: {
          ...(search && {
            [models.Op.or]: [
              {
                "$stdLearningMonitoring.learningUser.students.NISN$": {
                  [models.Op.like]: `%${search}%`,
                },
              },
              {
                "$stdLearningMonitoring.learningUser.NAME_USERS$": {
                  [models.Op.like]: `%${search}%`,
                },
              },
              {
                "$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$":
                  {
                    [models.Op.like]: `%${search}%`,
                  },
              },
              {
                "$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": {
                  [models.Op.like]: `%${search}%`,
                },
              },
            ],
          }),
        },
        distinct: true,
      });

    const formattedResult = monitorings.map((monitoring) => ({
      ID_MONITORING: monitoring.ID_MONITORING,
      NISN: monitoring.stdLearningMonitoring?.learningUser?.students?.NISN,
      NAME_USERS: monitoring.stdLearningMonitoring?.learningUser?.NAME_USERS,
      NAME_SECTION:
        monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection
          ?.NAME_SECTION,
      NAME_TOPIC:
        monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC,
      NAME_CLASS:
        monitoring.stdLearningMonitoring?.learningUser?.students?.studentClass
          ?.NAME_CLASS ?? null,
    }));

    if (sort === "nisn") {
      formattedResult.sort((a, b) =>
        String(a.NISN).localeCompare(String(b.NISN))
      );
    } else if (sort === "name") {
      formattedResult.sort((a, b) => a.NAME_USERS.localeCompare(b.NAME_USERS));
    } else if (sort === "section") {
      formattedResult.sort((a, b) =>
        a.NAME_SECTION.localeCompare(b.NAME_SECTION)
      );
    } else if (sort === "topic") {
      formattedResult.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC));
    } else {
      formattedResult.sort((a, b) => new Date(b.TIME) - new Date(a.TIME));
    }

    const paginatedResult = formattedResult.slice(
      (page - 1) * limit,
      page * limit
    );
    const totalPages = Math.ceil(count / limit);
    const currentPage = parseInt(page);

    response(
      200,
      {
        monitorings: paginatedResult,
        currentPage,
        totalPages,
        totalItems: count,
      },
      "Success retrieving student monitoring progress!",
      res
    );
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving student monitoring progress!", res);
  }
};

export const monitoringStudentProgressById = async (req, res) => {
  const { id } = req.params;
  const { page = 1, limit = 10, search = "", sort = "start" } = req.query;

  try {
    const monitoring = await models.Monitoring.findOne({
      where: { ID_MONITORING: id },
      include: [
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          attributes: [
            "ID",
            "ID_LEVEL",
            "SCORE",
            "FEEDBACK_STUDENT",
            "STUDENT_START",
            "STUDENT_FINISH",
            "ID_STUDENT_LEARNING",
          ],
          include: [
            {
              model: models.Level,
              as: "level",
              attributes: ["ID_TOPIC", "NAME_LEVEL"],
              include: [
                {
                  model: models.Topic,
                  as: "levelTopic",
                  attributes: ["NAME_TOPIC"],
                  include: [
                    {
                      model: models.Section,
                      as: "topicSection",
                      attributes: ["NAME_SECTION"],
                    },
                  ],
                },
              ],
            },
            {
              model: models.User,
              as: "learningUser",
              attributes: ["NAME_USERS"],
              include: [
                {
                  model: models.Student,
                  as: "students",
                  attributes: ["NISN"],
                },
              ],
            },
          ],
        },
      ],
    });

    if (!monitoring) {
      return response(404, null, "Monitoring data not found!", res);
    }

    const stdLearning = monitoring.stdLearningMonitoring;

    if (!stdLearning || stdLearning.length === 0) {
      return response(404, null, "No student learning data found!", res);
    }

    const userID = stdLearning.ID;
    const topicID = stdLearning.level.ID_TOPIC;
    const studentName = stdLearning.learningUser.NAME_USERS;
    const nisn = stdLearning.learningUser.students.NISN;
    const topicName = stdLearning.level.levelTopic.NAME_TOPIC;
    const sectionName = stdLearning.level.levelTopic.topicSection.NAME_SECTION;

    const levels = await models.StdLearning.findAndCountAll({
      where: {
        ID: userID,
        ...(search && {
          [models.Op.or]: [
            {
              "$level.NAME_LEVEL$": { [models.Op.like]: `%${search}%` },
            },
            {
              SCORE: { [models.Op.like]: `%${search}%` },
            },
            {
              FEEDBACK_STUDENT: { [models.Op.like]: `%${search}%` },
            },
          ],
        }),
        STUDENT_FINISH: {
          [models.Sequelize.Op.ne]: null,
        },
      },
      include: [
        {
          model: models.Level,
          as: "level",
          attributes: ["NAME_LEVEL", "ID_TOPIC"],
          where: {
            ID_TOPIC: topicID,
          },
        },
      ],
      attributes: [
        "SCORE",
        "FEEDBACK_STUDENT",
        "STUDENT_START",
        "STUDENT_FINISH",
      ],
      distinct: true,
    });

    const levelArray = levels.rows
      .map((learning) => ({
        NAME_LEVEL: learning.level.NAME_LEVEL,
        SCORE: learning.SCORE,
        FEEDBACK_STUDENT: learning.FEEDBACK_STUDENT,
        STUDENT_START: learning.STUDENT_START,
        STUDENT_FINISH: learning.STUDENT_FINISH,
      }))
      .sort((a, b) => {
        if (sort === "level") {
          return a.NAME_LEVEL.localeCompare(b.NAME_LEVEL);
        } else if (sort === "score") {
          return b.SCORE - a.SCORE;
        } else if (sort === "feedback") {
          if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT !== null) {
            return 1;
          } else if (
            a.FEEDBACK_STUDENT !== null &&
            b.FEEDBACK_STUDENT === null
          ) {
            return -1;
          } else if (
            a.FEEDBACK_STUDENT === null &&
            b.FEEDBACK_STUDENT === null
          ) {
            return 0;
          } else {
            return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
          }
        } else if (sort === "start") {
          return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
        } else if (sort === "finish") {
          return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH);
        }
        return 0;
      });

    const paginatedResult = levelArray.slice((page - 1) * limit, page * limit);
    const totalPages = Math.ceil(levels.count / limit);
    const currentPage = parseInt(page);

    const result = {
      ID_MONITORING: monitoring.ID_MONITORING,
      NAME_SECTION: sectionName,
      NAME_TOPIC: topicName,
      NAME_USERS: studentName,
      NISN: nisn,
      levels: paginatedResult,
      currentPage,
      totalPages,
      totalItems: levels.count,
    };

    response(200, result, "Success retrieving student progress!", res);
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving student progress!", res);
  }
};

export const monitoringFeedback = async (req, res) => {
  const { id } = req.params;
  const { feedback } = req.body;

  if (!req.user) {
    return response(401, null, "User not authenticated", res);
  }

  const { ID } = req.user;

  if (!ID) {
    return response(401, null, "Unauthorized: User ID not provided", res);
  }

  try {
    const teacher = await models.Teacher.findOne({
      where: { ID: ID },
      attributes: ["ID_GURU"],
    });

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

    const [updatedRows] = await models.Monitoring.update(
      {
        ID_GURU: teacher.ID_GURU,
        FEEDBACK_GURU: feedback,
      },
      {
        where: { ID_MONITORING: id },
        returning: true,
      }
    );

    if (updatedRows === 0) {
      return response(404, null, "Monitoring data not found!", res);
    }

    const monitoringWithRelations = await models.Monitoring.findOne({
      where: { ID_MONITORING: id },
      include: [
        {
          model: models.Teacher,
          as: "monitoringTeacher",
          attributes: ["ID_GURU"],
          include: [
            {
              model: models.User,
              as: "teacherUser",
              attributes: ["NAME_USERS"],
            },
          ],
        },
      ],
    });

    if (!monitoringWithRelations) {
      return response(404, null, "Updated monitoring data not found!", res);
    }

    const result = {
      ID_MONITORING: monitoringWithRelations.ID_MONITORING,
      FEEDBACK_GURU: monitoringWithRelations.FEEDBACK_GURU,
      ID_GURU: monitoringWithRelations.monitoringTeacher?.ID_GURU,
      TEACHER_NAME:
        monitoringWithRelations.monitoringTeacher?.teacherUser?.NAME_USERS,
    };

    response(200, result, "Success updating teacher feedback!", res);
  } catch (error) {
    console.error("Error in monitoringFeedback:", error);
    response(500, null, "Error updating teacher feedback!", res);
  }
};

export const getMonitoringByTopicId = async (req, res) => {
  const { topicId } = req.params;

  if (!req.user) {
    return response(401, null, "User not authenticated", res);
  }

  const { ID } = req.user;

  if (!ID) {
    return response(401, null, "Unauthorized: User ID not provided", res);
  }

  try {
    const stdLearning = await models.StdLearning.findOne({
      where: {
        ID: ID,
      },
      include: [
        {
          model: models.Level,
          as: "level",
          where: { ID_TOPIC: topicId },
          attributes: ["ID_LEVEL", "NAME_LEVEL"],
          include: [
            {
              model: models.Topic,
              as: "levelTopic",
              attributes: ["NAME_TOPIC"],
            },
          ],
        },
      ],
    });

    if (!stdLearning) {
      return response(
        404,
        null,
        "Student learning data not found for this topic!",
        res
      );
    }

    const monitoringData = await models.Monitoring.findOne({
      where: {
        ID_STUDENT_LEARNING: stdLearning.ID_STUDENT_LEARNING,
      },
      include: [
        {
          model: models.Teacher,
          as: "monitoringTeacher",
          attributes: ["ID_GURU"],
          include: [
            {
              model: models.User,
              as: "teacherUser",
              attributes: ["NAME_USERS"],
            },
          ],
        },
      ],
    });

    if (!monitoringData) {
      return response(
        404,
        null,
        "Monitoring data not found for this learning record!",
        res
      );
    }

    const result = {
      ID_MONITORING: monitoringData.ID_MONITORING,
      NAME_TOPIC: stdLearning.level?.levelTopic?.NAME_TOPIC,
      NAME_LEVEL: stdLearning.level?.NAME_LEVEL,
      TEACHER_NAME: monitoringData.monitoringTeacher?.teacherUser?.NAME_USERS,
      FEEDBACK_GURU: monitoringData.FEEDBACK_GURU,
      TIME_MONITORING: monitoringData.TIME_MONITORING,
    };

    response(200, result, "Success retrieving monitoring data!", res);
  } catch (error) {
    console.error("Error in getMonitoringByTopicId:", error);
    response(500, null, "Error retrieving monitoring data!", res);
  }
};

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

  try {
    const searchCondition = search
      ? {
          [models.Op.or]: [
            {
              "$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$":
                { [models.Op.like]: `%${search}%` },
            },
            {
              "$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": {
                [models.Op.like]: `%${search}%`,
              },
            },
          ],
        }
      : {};

    const { rows: monitoringRecords, count } =
      await models.Monitoring.findAndCountAll({
        where: { ID_CLASS: classId, ...searchCondition },
        include: [
          {
            model: models.StdLearning,
            as: "stdLearningMonitoring",
            include: [
              {
                model: models.Level,
                as: "level",
                attributes: ["ID_LEVEL"],
                include: [
                  {
                    model: models.Topic,
                    as: "levelTopic",
                    attributes: ["ID_TOPIC", "NAME_TOPIC"],
                    include: [
                      {
                        model: models.Section,
                        as: "topicSection",
                        attributes: ["ID_SECTION", "NAME_SECTION"],
                      },
                    ],
                  },
                ],
              },
            ],
          },
        ],
        distinct: true,
      });

    if (!monitoringRecords || monitoringRecords.length === 0) {
      return response(
        404,
        null,
        "No monitoring data found for this class!",
        res
      );
    }

    const uniqueRecords = new Map();
    monitoringRecords.forEach((monitoring) => {
      const sectionId =
        monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection
          ?.ID_SECTION;
      const sectionName =
        monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection
          ?.NAME_SECTION;
      const topicId =
        monitoring.stdLearningMonitoring?.level?.levelTopic?.ID_TOPIC;
      const topicName =
        monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC;

      const key = `${sectionId}-${topicId}`;
      if (!uniqueRecords.has(key)) {
        uniqueRecords.set(key, {
          ID_SECTION: sectionId,
          ID_TOPIC: topicId,
          NAME_SECTION: sectionName,
          NAME_TOPIC: topicName,
        });
      }
    });

    const sortedRecords = Array.from(uniqueRecords.values());

    if (sort === "section") {
      sortedRecords.sort((a, b) =>
        a.NAME_SECTION.localeCompare(b.NAME_SECTION)
      );
    } else if (sort === "topic") {
      sortedRecords.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC));
    }

    const paginatedRecords = sortedRecords.slice(
      (page - 1) * limit,
      page * limit
    );

    const totalPages = Math.ceil(uniqueRecords.size / limit);

    const paginatedResult = {
      data: paginatedRecords,
      currentPage: parseInt(page),
      totalPages,
      totalItems: uniqueRecords.size,
    };

    response(
      200,
      paginatedResult,
      "Success retrieving unique monitoring section and topic data!",
      res
    );
  } catch (error) {
    console.error("Error in getClassMonitoringByClassId:", error);
    response(
      500,
      null,
      "Error retrieving monitoring section and topic data!",
      res
    );
  }
};

export const getClassMonitoringDataByClassAndTopic = async (req, res) => {
  const { ID_CLASS, ID_TOPIC } = req.body;
  const { page = 1, limit = 10, search = "", sort = "finish" } = req.query;

  try {
    const classData = await models.Class.findOne({
      where: { ID_CLASS: ID_CLASS },
      attributes: ["ID_CLASS", "NAME_CLASS"],
    });

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

    const className = classData.NAME_CLASS;
    const classId = classData.ID_CLASS;

    const topicData = await models.Topic.findOne({
      where: { ID_TOPIC: ID_TOPIC },
      include: [
        {
          model: models.Section,
          as: "topicSection",
          attributes: ["ID_SECTION", "NAME_SECTION"],
        },
      ],
      attributes: ["ID_TOPIC", "NAME_TOPIC"],
    });

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

    const topicName = topicData.NAME_TOPIC;
    const topicId = topicData.ID_TOPIC;
    const sectionName = topicData.topicSection?.NAME_SECTION;
    const sectionId = topicData.topicSection?.ID_SECTION;

    const monitoringData = await models.Monitoring.findAll({
      where: { ID_CLASS: ID_CLASS },
      include: [
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          required: true,
          attributes: ["ID", "ID_LEVEL"],
          include: [
            {
              model: models.Level,
              as: "level",
              attributes: ["ID_LEVEL", "ID_TOPIC"],
              where: { ID_TOPIC: ID_TOPIC },
              required: true,
            },
          ],
        },
      ],
    });

    if (!monitoringData || monitoringData.length === 0) {
      const result = {
        ID_CLASS: classId,
        ID_SECTION: sectionId,
        ID_TOPIC: topicId,
        NAME_CLASS: className,
        NAME_SECTION: sectionName,
        NAME_TOPIC: topicName,
      };
      return response(
        200,
        result,
        "No monitoring data found for this class and topic!",
        res
      );
    }

    const userIds = monitoringData.map(
      (monitoring) => monitoring.stdLearningMonitoring.ID
    );

    const searchCondition = search
      ? {
          [models.Sequelize.Op.or]: [
            {
              "$learningUser.NAME_USERS$": {
                [models.Sequelize.Op.like]: `%${search}%`,
              },
            },
            {
              "$learningUser.students.NISN$": {
                [models.Sequelize.Op.like]: `%${search}%`,
              },
            },
            {
              "$level.NAME_LEVEL$": {
                [models.Sequelize.Op.like]: `%${search}%`,
              },
            },
            { $SCORE$: { [models.Sequelize.Op.like]: `%${search}%` } },
            {
              $FEEDBACK_STUDENT$: { [models.Sequelize.Op.like]: `%${search}%` },
            },
          ],
        }
      : {};

    const { rows: allStdLearning, count } =
      await models.StdLearning.findAndCountAll({
        where: {
          ID_LEVEL: {
            [models.Sequelize.Op.in]: (
              await models.Level.findAll({
                where: { ID_TOPIC: ID_TOPIC },
                attributes: ["ID_LEVEL"],
              })
            ).map((level) => level.ID_LEVEL),
          },
          ID: {
            [models.Sequelize.Op.in]: userIds,
          },
          STUDENT_FINISH: {
            [models.Sequelize.Op.ne]: null,
          },
          ...searchCondition,
        },
        include: [
          {
            model: models.Level,
            as: "level",
            attributes: ["NAME_LEVEL", "ID_TOPIC"],
          },
          {
            model: models.User,
            as: "learningUser",
            attributes: ["NAME_USERS"],
            include: [
              {
                model: models.Student,
                as: "students",
                attributes: ["NISN"],
              },
            ],
          },
        ],
        attributes: [
          "SCORE",
          "FEEDBACK_STUDENT",
          "STUDENT_START",
          "STUDENT_FINISH",
        ],
        order: [["STUDENT_FINISH", "DESC"]],
      });

    const sortedRecords = allStdLearning.sort((a, b) => {
      const userComparison = a.learningUser.NAME_USERS.localeCompare(
        b.learningUser.NAME_USERS
      );
      if (userComparison !== 0) {
        return userComparison;
      }

      if (sort === "nisn") {
        return String(a.learningUser.students.NISN).localeCompare(
          String(b.learningUser.students.NISN)
        );
      } else if (sort === "name") {
        return userComparison;
      } else if (sort === "level") {
        return a.level.NAME_LEVEL.localeCompare(b.level.NAME_LEVEL);
      } else if (sort === "score") {
        return b.SCORE - a.SCORE;
      } else if (sort === "feedback") {
        if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT !== null) {
          return 1;
        } else if (a.FEEDBACK_STUDENT !== null && b.FEEDBACK_STUDENT === null) {
          return -1;
        } else if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT === null) {
          return 0;
        } else {
          return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
        }
      } else if (sort === "start") {
        return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
      }

      return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH);
    });

    const paginatedRecords = sortedRecords.slice(
      (page - 1) * limit,
      page * limit
    );
    const totalPages = Math.ceil(count / limit);

    const formattedData = paginatedRecords.map((stdLearning) => {
      const level = stdLearning?.level;
      const user = stdLearning?.learningUser;
      const student = user?.students;

      return {
        NISN: student?.NISN,
        NAME_USERS: user?.NAME_USERS,
        NAME_LEVEL: level?.NAME_LEVEL,
        SCORE: stdLearning?.SCORE,
        FEEDBACK_STUDENT: stdLearning?.FEEDBACK_STUDENT,
        STUDENT_START: stdLearning?.STUDENT_START,
        STUDENT_FINISH: stdLearning?.STUDENT_FINISH,
      };
    });

    const result = {
      ID_CLASS: classId,
      ID_SECTION: sectionId,
      ID_TOPIC: topicId,
      NAME_CLASS: className,
      NAME_SECTION: sectionName,
      NAME_TOPIC: topicName,
      levels: formattedData,
      currentPage: parseInt(page),
      totalPages,
      totalItems: count,
    };

    response(200, result, "Success retrieving monitoring data!", res);
  } catch (error) {
    console.error("Error fetching monitoring data:", error);
    response(500, null, "Error retrieving monitoring data!", res);
  }
};

export const monitoringFeedbackByClassAndTopic = async (req, res) => {
  const { ID_CLASS, ID_TOPIC, FEEDBACK } = req.body;

  if (!req.user) {
    return response(401, null, "User not authenticated", res);
  }

  const { ID } = req.user;

  if (!ID) {
    return response(401, null, "Unauthorized: User ID not provided", res);
  }

  try {
    const teacher = await models.Teacher.findOne({
      where: { ID },
      attributes: ["ID_GURU"],
    });

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

    const monitoringData = await models.Monitoring.findAll({
      where: { ID_CLASS },
      include: [
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          required: true,
          include: [
            {
              model: models.Level,
              as: "level",
              where: { ID_TOPIC },
              attributes: [],
            },
          ],
        },
      ],
    });

    if (!monitoringData || monitoringData.length === 0) {
      return response(
        404,
        null,
        "No monitoring data found for this class and topic!",
        res
      );
    }

    for (const monitoring of monitoringData) {
      const [updatedRows] = await models.Monitoring.update(
        {
          ID_GURU: teacher.ID_GURU,
          FEEDBACK_GURU: FEEDBACK,
        },
        {
          where: { ID_MONITORING: monitoring.ID_MONITORING },
        }
      );
    }

    const updatedMonitoringData = await models.Monitoring.findAll({
      where: { ID_CLASS: ID_CLASS },
      include: [
        {
          model: models.Teacher,
          as: "monitoringTeacher",
          attributes: ["ID_GURU"],
          include: [
            {
              model: models.User,
              as: "teacherUser",
              attributes: ["NAME_USERS"],
            },
          ],
        },
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          required: true,
          include: [
            {
              model: models.Level,
              as: "level",
              where: { ID_TOPIC },
            },
          ],
        },
      ],
    });

    const result = updatedMonitoringData.map((monitoring) => ({
      ID_MONITORING: monitoring.ID_MONITORING,
      FEEDBACK_GURU: monitoring.FEEDBACK_GURU,
      ID_GURU: monitoring.monitoringTeacher?.ID_GURU,
      TEACHER_NAME: monitoring.monitoringTeacher?.teacherUser?.NAME_USERS,
    }));

    response(
      200,
      result,
      "Success updating teacher feedback for class and topic!",
      res
    );
  } catch (error) {
    console.error("Error in monitoringFeedbackByClassAndTopic:", error);
    response(500, null, "Error updating teacher feedback!", res);
  }
};

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

  try {
    const monitoring = await models.Monitoring.findOne({
      where: { ID_MONITORING: id },
      include: [
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          attributes: [
            "ID",
            "ID_LEVEL",
            "SCORE",
            "FEEDBACK_STUDENT",
            "STUDENT_START",
            "STUDENT_FINISH",
            "ID_STUDENT_LEARNING",
          ],
          include: [
            {
              model: models.Level,
              as: "level",
              attributes: ["ID_TOPIC", "NAME_LEVEL"],
              include: [
                {
                  model: models.Topic,
                  as: "levelTopic",
                  attributes: ["NAME_TOPIC"],
                  include: [
                    {
                      model: models.Section,
                      as: "topicSection",
                      attributes: ["NAME_SECTION"],
                    },
                  ],
                },
              ],
            },
            {
              model: models.User,
              as: "learningUser",
              attributes: ["NAME_USERS"],
              include: [
                {
                  model: models.Student,
                  as: "students",
                  attributes: ["NISN"],
                },
              ],
            },
          ],
        },
      ],
    });

    if (!monitoring) {
      return response(404, null, "Monitoring data not found!", res);
    }

    const stdLearning = monitoring.stdLearningMonitoring;

    if (!stdLearning || stdLearning.length === 0) {
      return response(404, null, "No student learning data found!", res);
    }

    const userID = stdLearning.ID;
    const topicID = stdLearning.level.ID_TOPIC;
    const studentName = stdLearning.learningUser.NAME_USERS;
    const nisn = stdLearning.learningUser.students.NISN;
    const topicName = stdLearning.level.levelTopic.NAME_TOPIC;
    const sectionName = stdLearning.level.levelTopic.topicSection.NAME_SECTION;

    const levels = await models.StdLearning.findAll({
      where: {
        ID: userID,
        STUDENT_FINISH: {
          [models.Sequelize.Op.ne]: null,
        },
      },
      include: [
        {
          model: models.Level,
          as: "level",
          attributes: ["NAME_LEVEL", "ID_TOPIC"],
          where: {
            ID_TOPIC: topicID,
          },
        },
      ],
      attributes: [
        "SCORE",
        "FEEDBACK_STUDENT",
        "STUDENT_START",
        "STUDENT_FINISH",
      ],
      order: [["STUDENT_FINISH", "DESC"]],
      distinct: true,
    });

    const levelArray = levels.map((learning) => ({
      NAME_LEVEL: learning.level.NAME_LEVEL,
      SCORE: learning.SCORE,
      FEEDBACK_STUDENT: learning.FEEDBACK_STUDENT,
      STUDENT_START: learning.STUDENT_START,
      STUDENT_FINISH: learning.STUDENT_FINISH,
    }));

    const tempDir = os.tmpdir();
    const tempFilePath = path.join(
      tempDir,
      `Student_Monitoring_${nisn}_${studentName}.csv`
    );

    const csvWriter = createObjectCsvWriter({
      path: tempFilePath,
      header: [
        { id: "field", title: "Field" },
        { id: "value", title: "Value" },
      ],
    });

    const records = [
      { field: "Section Name", value: sectionName },
      { field: "Topic Name", value: topicName },
      { field: "Student Name", value: studentName },
      { field: "NISN", value: nisn },
    ];

    await csvWriter.writeRecords(records);

    const levelCsvWriter = createObjectCsvWriter({
      path: tempFilePath,
      append: true,
      header: [
        { id: "NAME_LEVEL", title: "Level Name" },
        { id: "SCORE", title: "Score" },
        { id: "FEEDBACK_STUDENT", title: "Student Feedback" },
        { id: "STUDENT_START", title: "Start Exercise" },
        { id: "STUDENT_FINISH", title: "Finish Exercise" },
      ],
    });

    fs.appendFileSync(tempFilePath, "\n");

    fs.appendFileSync(
      tempFilePath,
      "Name Level,Score,Student Feedback,Start Exercise,Finish Exercise\n"
    );

    await levelCsvWriter.writeRecords(levelArray);

    res.setHeader("Content-Type", "text/csv");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="Student_Monitoring_${nisn}_${studentName}.csv.csv"`
    );

    const fileStream = fs.createReadStream(tempFilePath);
    fileStream.pipe(res).on("finish", () => {
      fs.unlinkSync(tempFilePath);
    });
  } catch (error) {
    console.error(error);
    response(500, null, "Error retrieving student progress!", res);
  }
};

export const classMonitoringCSVByClassAndTopic = async (req, res) => {
  const { ID_CLASS, ID_TOPIC } = req.body;

  try {
    const classData = await models.Class.findOne({
      where: { ID_CLASS: ID_CLASS },
      attributes: ["ID_CLASS", "NAME_CLASS"],
    });

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

    const className = classData.NAME_CLASS;

    const topicData = await models.Topic.findOne({
      where: { ID_TOPIC: ID_TOPIC },
      include: [
        {
          model: models.Section,
          as: "topicSection",
          attributes: ["ID_SECTION", "NAME_SECTION"],
        },
      ],
      attributes: ["ID_TOPIC", "NAME_TOPIC"],
    });

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

    const topicName = topicData.NAME_TOPIC;
    const sectionName = topicData.topicSection?.NAME_SECTION;

    const monitoringData = await models.Monitoring.findAll({
      where: { ID_CLASS: ID_CLASS },
      include: [
        {
          model: models.StdLearning,
          as: "stdLearningMonitoring",
          required: true,
          attributes: ["ID", "ID_LEVEL"],
          include: [
            {
              model: models.Level,
              as: "level",
              attributes: ["ID_LEVEL", "ID_TOPIC"],
              where: { ID_TOPIC: ID_TOPIC },
              required: true,
            },
          ],
        },
      ],
    });

    if (!monitoringData || monitoringData.length === 0) {
      const result = {
        ID_CLASS: classData.ID_CLASS,
        ID_SECTION: topicData.topicSection?.ID_SECTION,
        ID_TOPIC: topicData.ID_TOPIC,
        NAME_CLASS: className,
        NAME_SECTION: sectionName,
        NAME_TOPIC: topicName,
      };
      return response(
        200,
        result,
        "No monitoring data found for this class and topic!",
        res
      );
    }

    const userIds = monitoringData.map(
      (monitoring) => monitoring.stdLearningMonitoring.ID
    );

    const allStdLearning = await models.StdLearning.findAll({
      where: {
        ID_LEVEL: {
          [models.Sequelize.Op.in]: (
            await models.Level.findAll({
              where: { ID_TOPIC: ID_TOPIC },
              attributes: ["ID_LEVEL"],
            })
          ).map((level) => level.ID_LEVEL),
        },
        ID: {
          [models.Sequelize.Op.in]: userIds,
        },
        STUDENT_FINISH: {
          [models.Sequelize.Op.ne]: null,
        },
      },
      include: [
        {
          model: models.Level,
          as: "level",
          attributes: ["NAME_LEVEL", "ID_TOPIC"],
        },
        {
          model: models.User,
          as: "learningUser",
          attributes: ["NAME_USERS"],
          include: [
            {
              model: models.Student,
              as: "students",
              attributes: ["NISN"],
            },
          ],
        },
      ],
      attributes: [
        "SCORE",
        "FEEDBACK_STUDENT",
        "STUDENT_START",
        "STUDENT_FINISH",
      ],
      order: [
        [{ model: models.User, as: "learningUser" }, "NAME_USERS", "ASC"],
        ["STUDENT_FINISH", "DESC"],
      ],
    });

    const formattedData = allStdLearning.map((stdLearning) => {
      const level = stdLearning?.level;
      const user = stdLearning?.learningUser;
      const student = user?.students;

      return {
        NISN: student?.NISN,
        NAME_USERS: user?.NAME_USERS,
        NAME_LEVEL: level?.NAME_LEVEL,
        SCORE: stdLearning?.SCORE,
        FEEDBACK_STUDENT: stdLearning?.FEEDBACK_STUDENT,
        STUDENT_START: stdLearning?.STUDENT_START,
        STUDENT_FINISH: stdLearning?.STUDENT_FINISH,
      };
    });

    const sanitizedClassName = className.replace(/\s+/g, "_");
    const sanitizedTopicName = topicName.replace(/\s+/g, "_");

    const tempDir = os.tmpdir();
    const tempFilePath = path.join(
      tempDir,
      `Class_Monitoring_${sanitizedClassName}_${sanitizedTopicName}.csv`
    );

    const csvWriter = createObjectCsvWriter({
      path: tempFilePath,
      header: [
        { id: "field", title: "Field" },
        { id: "value", title: "Value" },
      ],
    });

    const records = [
      { field: "Class Name", value: className },
      { field: "Section Name", value: sectionName },
      { field: "Topic Name", value: topicName },
    ];

    await csvWriter.writeRecords(records);

    const levelCsvWriter = createObjectCsvWriter({
      path: tempFilePath,
      append: true,
      header: [
        { id: "NISN", title: "NISN" },
        { id: "NAME_USERS", title: "Student Name" },
        { id: "NAME_LEVEL", title: "Level Name" },
        { id: "SCORE", title: "Score" },
        { id: "FEEDBACK_STUDENT", title: "Student Feedback" },
        { id: "STUDENT_START", title: "Start Exercise" },
        { id: "STUDENT_FINISH", title: "Finish Exercise" },
      ],
    });

    fs.appendFileSync(tempFilePath, "\n");

    fs.appendFileSync(
      tempFilePath,
      "NISN,Student Name,Level Name,Score,Student Feedback,Start Exercise,Finish Exercise\n"
    );

    await levelCsvWriter.writeRecords(formattedData);

    res.setHeader("Content-Type", "text/csv");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename=Class_Monitoring_${sanitizedClassName}_${sanitizedTopicName}.csv`
    );

    const fileStream = fs.createReadStream(tempFilePath);
    fileStream.pipe(res).on("finish", () => {
      fs.unlinkSync(tempFilePath);
    });
  } catch (error) {
    console.error("Error fetching monitoring data:", error);
    response(500, null, "Error retrieving monitoring data!", res);
  }
};
