
const express = require('express');

const Attendance_sessionsService = require('../services/attendance_sessions');
const Attendance_sessionsDBApi = require('../db/api/attendance_sessions');
const ValidationError = require('../services/notifications/errors/validation');
const db = require('../db/models');
const Sequelize = require('sequelize');
const wrapAsync = require('../helpers').wrapAsync;

const { Op } = Sequelize;
const router = express.Router();

const { parse } = require('json2csv');


const {
    checkCrudPermissions,
} = require('../middlewares/check-permissions');

router.use(checkCrudPermissions('attendance_sessions'));



const ATTENDANCE_STATUSES = ['present', 'absent', 'late', 'excused'];
const SECTION_STATUS_ORDER = {
  active: 0,
  planned: 1,
  completed: 2,
  archived: 3,
};

const createValidationError = (message) => {
  const error = new ValidationError();
  error.message = message;
  return error;
};

const buildStudentDisplayName = (student) => {
  if (!student) {
    return 'Student';
  }

  const fullName = [student.user?.firstName, student.user?.lastName].filter(Boolean).join(' ').trim();
  return fullName || student.user?.email || student.student_number || 'Student';
};

const addStudentToMap = (rosterMap, student, source, lastStatus = null) => {
  if (!student?.id) {
    return;
  }

  const existingStudent = rosterMap.get(student.id);

  if (!existingStudent) {
    rosterMap.set(student.id, {
      studentId: student.id,
      displayName: buildStudentDisplayName(student),
      studentNumber: student.student_number || null,
      source,
      lastStatus,
    });
    return;
  }

  if (!existingStudent.lastStatus && lastStatus) {
    existingStudent.lastStatus = lastStatus;
  }

  if (!existingStudent.source && source) {
    existingStudent.source = source;
  }
};

const countSessionSummary = (records = []) => {
  const summary = {
    present: 0,
    absent: 0,
    late: 0,
    excused: 0,
  };

  records.forEach((record) => {
    if (record?.attendance_status && summary[record.attendance_status] !== undefined) {
      summary[record.attendance_status] += 1;
    }
  });

  return summary;
};

const findTeacherProfilesForUser = async (currentUser) => {
  if (Array.isArray(currentUser?.teachers_user) && currentUser.teachers_user.length > 0) {
    return currentUser.teachers_user;
  }

  if (!currentUser?.id) {
    return [];
  }

  return db.teachers.findAll({
    where: { userId: currentUser.id },
    attributes: ['id'],
  });
};

const fetchAvailableSections = async (currentUser) => {
  const teacherProfiles = await findTeacherProfilesForUser(currentUser);
  const teacherIds = teacherProfiles.map((teacher) => teacher.id).filter(Boolean);
  const isAdministrator = currentUser?.app_role?.name === 'Administrator';
  const where = !isAdministrator && teacherIds.length ? { teacherId: { [Op.in]: teacherIds } } : {};

  const sections = await db.course_sections.findAll({
    where,
    include: [
      { model: db.subjects, as: 'subject', attributes: ['name'], required: false },
      { model: db.terms, as: 'term', attributes: ['name'], required: false },
      { model: db.classes, as: 'class', attributes: ['name'], required: false },
      {
        model: db.teachers,
        as: 'teacher',
        attributes: ['id'],
        required: false,
        include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
      },
    ],
    order: [['updatedAt', 'DESC']],
  });

  return sections
    .map((section) => ({
      id: section.id,
      name: section.name,
      section_code: section.section_code,
      status: section.status,
      subjectName: section.subject?.name || null,
      termName: section.term?.name || null,
      className: section.class?.name || null,
      teacherName: section.teacher?.user ? buildStudentDisplayName({ user: section.teacher.user }) : null,
    }))
    .sort((left, right) => {
      const leftWeight = SECTION_STATUS_ORDER[left.status] ?? 99;
      const rightWeight = SECTION_STATUS_ORDER[right.status] ?? 99;

      if (leftWeight !== rightWeight) {
        return leftWeight - rightWeight;
      }

      return (left.name || '').localeCompare(right.name || '');
    });
};

const findCourseSectionOrThrow = async (courseSectionId) => {
  const section = await db.course_sections.findByPk(courseSectionId, {
    include: [
      { model: db.subjects, as: 'subject', attributes: ['name'], required: false },
      { model: db.terms, as: 'term', attributes: ['name'], required: false },
      { model: db.classes, as: 'class', attributes: ['id', 'name'], required: false },
      {
        model: db.teachers,
        as: 'teacher',
        attributes: ['id'],
        required: false,
        include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
      },
    ],
  });

  if (!section) {
    const error = new Error('Course section not found');
    error.code = 404;
    throw error;
  }

  return section;
};

const fetchRecentSessions = async (courseSectionId) => {
  const sessions = await db.attendance_sessions.findAll({
    where: { course_sectionId: courseSectionId },
    include: [
      {
        model: db.attendance_records,
        as: 'attendance_records_attendance_session',
        attributes: ['attendance_status'],
        required: false,
      },
    ],
    order: [['session_start', 'DESC']],
    limit: 5,
  });

  return sessions.map((session) => {
    const sessionRecords = session.attendance_records_attendance_session || [];

    return {
      id: session.id,
      session_start: session.session_start,
      session_end: session.session_end,
      status: session.status,
      notes: session.notes,
      summary: countSessionSummary(sessionRecords),
      totalRecords: sessionRecords.length,
    };
  });
};

const buildRosterForSection = async (courseSection) => {
  const rosterMap = new Map();
  let rosterSource = null;

  if (courseSection.classId) {
    const enrollments = await db.class_enrollments.findAll({
      where: { classId: courseSection.classId },
      include: [
        {
          model: db.students,
          as: 'student',
          required: false,
          include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
        },
      ],
      order: [['createdAt', 'ASC']],
    });

    enrollments.forEach((enrollment) => addStudentToMap(rosterMap, enrollment.student, 'class enrollment'));

    if (enrollments.length > 0) {
      rosterSource = 'class enrollment';
    }
  }

  const attendanceHistory = await db.attendance_records.findAll({
    include: [
      {
        model: db.attendance_sessions,
        as: 'attendance_session',
        required: true,
        where: { course_sectionId: courseSection.id },
        attributes: ['id', 'session_start'],
      },
      {
        model: db.students,
        as: 'student',
        required: false,
        include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
      },
    ],
    order: [['createdAt', 'DESC']],
  });

  attendanceHistory.forEach((record) => addStudentToMap(rosterMap, record.student, 'attendance history', record.attendance_status));

  if (!rosterSource && attendanceHistory.length > 0) {
    rosterSource = 'attendance history';
  }

  const submissions = await db.submissions.findAll({
    include: [
      {
        model: db.assignments,
        as: 'assignment',
        required: true,
        where: { course_sectionId: courseSection.id },
        attributes: ['id'],
      },
      {
        model: db.students,
        as: 'student',
        required: false,
        include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
      },
    ],
    order: [['createdAt', 'DESC']],
  });

  submissions.forEach((submission) => addStudentToMap(rosterMap, submission.student, 'assignment activity'));

  if (!rosterSource && submissions.length > 0) {
    rosterSource = 'assignment activity';
  }

  const gradeRecords = await db.grade_records.findAll({
    include: [
      {
        model: db.grade_items,
        as: 'grade_item',
        required: true,
        where: { course_sectionId: courseSection.id },
        attributes: ['id'],
      },
      {
        model: db.students,
        as: 'student',
        required: false,
        include: [{ model: db.users, as: 'user', attributes: ['firstName', 'lastName', 'email'], required: false }],
      },
    ],
    order: [['createdAt', 'DESC']],
  });

  gradeRecords.forEach((record) => addStudentToMap(rosterMap, record.student, 'gradebook activity'));

  if (!rosterSource && gradeRecords.length > 0) {
    rosterSource = 'gradebook activity';
  }

  return {
    rosterSource,
    roster: Array.from(rosterMap.values()).sort((left, right) =>
      left.displayName.localeCompare(right.displayName),
    ),
  };
};

/**
 *  @swagger
 *  components:
 *    schemas:
 *      Attendance_sessions:
 *        type: object
 *        properties:
 
 *          notes:
 *            type: string
 *            default: notes
 
 
 
 *          
 */

/**
 *  @swagger
 * tags:
 *   name: Attendance_sessions
 *   description: The Attendance_sessions managing API
 */

/**
*  @swagger
*  /api/attendance_sessions:
*    post:
*      security:
*        - bearerAuth: []
*      tags: [Attendance_sessions]
*      summary: Add new item
*      description: Add new item
*      requestBody:
*        required: true
*        content:
*          application/json:
*            schema:
*              properties:
*                data:
*                  description: Data of the updated item
*                  type: object
*                  $ref: "#/components/schemas/Attendance_sessions"
*      responses:
*        200:
*          description: The item was successfully added
*          content:
*            application/json:
*              schema:
*                $ref: "#/components/schemas/Attendance_sessions"
*        401:
*          $ref: "#/components/responses/UnauthorizedError"
*        405:
*          description: Invalid input data
*        500:
*          description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
    const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
    const link = new URL(referer);
    await Attendance_sessionsService.create(req.body.data, req.currentUser, true, link.host);
    const payload = true;
    res.status(200).send(payload);
}));

/**
 * @swagger
 * /api/budgets/bulk-import:
 *  post:
 *    security:
 *      - bearerAuth: []
 *    tags: [Attendance_sessions]
 *    summary: Bulk import items
 *    description: Bulk import items
 *    requestBody:
 *      required: true
 *      content:
 *        application/json:
 *         schema:
 *          properties:
 *            data:
 *              description: Data of the updated items
 *              type: array
 *              items:
 *                $ref: "#/components/schemas/Attendance_sessions"
 *    responses:
 *      200:
 *        description: The items were successfully imported
 *    content:
 *      application/json:
 *        schema:
 *          $ref: "#/components/schemas/Attendance_sessions"
 *      401:
 *        $ref: "#/components/responses/UnauthorizedError"
 *      405:
 *        description: Invalid input data
 *      500:
 *        description: Some server error
 *
 */
router.post('/bulk-import', wrapAsync(async (req, res) => {
    const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
    const link = new URL(referer);
    await Attendance_sessionsService.bulkImport(req, res, true, link.host);
    const payload = true;
    res.status(200).send(payload);
}));

/**
  *  @swagger
  *  /api/attendance_sessions/{id}:
  *    put:
  *      security:
  *        - bearerAuth: []
  *      tags: [Attendance_sessions]
  *      summary: Update the data of the selected item
  *      description: Update the data of the selected item
  *      parameters:
  *        - in: path
  *          name: id
  *          description: Item ID to update
  *          required: true
  *          schema:
  *            type: string
  *      requestBody:
  *        description: Set new item data
  *        required: true
  *        content:
  *          application/json:
  *            schema:
  *              properties:
  *                id:
  *                  description: ID of the updated item
  *                  type: string
  *                data:
  *                  description: Data of the updated item
  *                  type: object
  *                  $ref: "#/components/schemas/Attendance_sessions"
  *              required:
  *                - id
  *      responses:
  *        200:
  *          description: The item data was successfully updated
  *          content:
  *            application/json:
  *              schema:
  *                $ref: "#/components/schemas/Attendance_sessions"
  *        400:
  *          description: Invalid ID supplied
  *        401:
  *          $ref: "#/components/responses/UnauthorizedError"
  *        404:
  *          description: Item not found
  *        500:
  *          description: Some server error
  */

router.get('/workflow/overview', wrapAsync(async (req, res) => {
  const sections = await fetchAvailableSections(req.currentUser);
  const requestedSectionId = typeof req.query.courseSectionId === 'string' ? req.query.courseSectionId : '';
  const selectedSectionId = requestedSectionId || sections[0]?.id || null;

  let roster = [];
  let rosterSource = null;
  let recentSessions = [];

  if (selectedSectionId) {
    const courseSection = await findCourseSectionOrThrow(selectedSectionId);
    const rosterResult = await buildRosterForSection(courseSection);
    roster = rosterResult.roster;
    rosterSource = rosterResult.rosterSource;
    recentSessions = await fetchRecentSessions(selectedSectionId);
  }

  res.status(200).send({
    sections,
    selectedSectionId,
    roster,
    rosterSource,
    recentSessions,
    summary: {
      totalSections: sections.length,
      totalStudents: roster.length,
      recentSessionsCount: recentSessions.length,
    },
  });
}));

router.post('/workflow/submit', wrapAsync(async (req, res) => {
  const {
    courseSectionId,
    sessionDate,
    startTime,
    endTime,
    notes,
    status,
    records,
  } = req.body || {};

  if (!courseSectionId) {
    throw createValidationError('Course section is required');
  }

  if (!sessionDate || !startTime || !endTime) {
    throw createValidationError('Session date, start time, and end time are required');
  }

  if (!Array.isArray(records) || records.length === 0) {
    throw createValidationError('At least one attendance record is required');
  }

  const sessionStart = new Date(`${sessionDate}T${startTime}:00`);
  const sessionEnd = new Date(`${sessionDate}T${endTime}:00`);

  if (Number.isNaN(sessionStart.getTime()) || Number.isNaN(sessionEnd.getTime())) {
    throw createValidationError('Attendance session times are invalid');
  }

  if (sessionEnd <= sessionStart) {
    throw createValidationError('Attendance session end time must be later than the start time');
  }

  const courseSection = await findCourseSectionOrThrow(courseSectionId);
  const teacherProfiles = await findTeacherProfilesForUser(req.currentUser);
  const fallbackTeacherId = teacherProfiles[0]?.id || null;

  const normalizedRecords = records.map((record) => {
    if (!record?.studentId) {
      throw createValidationError('Each attendance row must include a student');
    }

    if (!ATTENDANCE_STATUSES.includes(record.attendance_status)) {
      throw createValidationError('Attendance status is invalid');
    }

    const minutesLate = Number(record.minutes_late || 0);

    if (record.attendance_status === 'late' && (Number.isNaN(minutesLate) || minutesLate < 0)) {
      throw createValidationError('Minutes late must be zero or greater');
    }

    return {
      studentId: record.studentId,
      attendance_status: record.attendance_status,
      minutes_late: record.attendance_status === 'late' ? minutesLate : 0,
      comment: typeof record.comment === 'string' ? record.comment.trim() : null,
    };
  });

  const summary = normalizedRecords.reduce(
    (accumulator, record) => {
      if (record.attendance_status === 'present') {
        accumulator.present += 1;
      } else {
        accumulator.flagged += 1;
      }

      accumulator.total += 1;
      return accumulator;
    },
    { total: 0, present: 0, flagged: 0 },
  );

  const session = await db.sequelize.transaction(async (transaction) => {
    const createdSession = await db.attendance_sessions.create(
      {
        session_start: sessionStart,
        session_end: sessionEnd,
        status: status === 'open' ? 'open' : 'closed',
        notes: typeof notes === 'string' && notes.trim() ? notes.trim() : null,
        course_sectionId: courseSection.id,
        teacherId: courseSection.teacherId || fallbackTeacherId,
        createdById: req.currentUser?.id || null,
        updatedById: req.currentUser?.id || null,
      },
      { transaction },
    );

    await db.attendance_records.bulkCreate(
      normalizedRecords.map((record) => ({
        attendance_status: record.attendance_status,
        minutes_late: record.minutes_late,
        comment: record.comment,
        attendance_sessionId: createdSession.id,
        studentId: record.studentId,
        createdById: req.currentUser?.id || null,
        updatedById: req.currentUser?.id || null,
      })),
      { transaction },
    );

    return createdSession;
  });

  res.status(200).send({
    sessionId: session.id,
    summary,
  });
}));

router.put('/:id', wrapAsync(async (req, res) => {
  await Attendance_sessionsService.update(req.body.data, req.body.id, req.currentUser);
  const payload = true;
  res.status(200).send(payload);
}));

/**
  * @swagger
  *  /api/attendance_sessions/{id}:
  *    delete:
  *      security:
  *        - bearerAuth: []
  *      tags: [Attendance_sessions]
  *      summary: Delete the selected item
  *      description: Delete the selected item
  *      parameters:
  *        - in: path
  *          name: id
  *          description: Item ID to delete
  *          required: true
  *          schema:
  *            type: string
  *      responses:
  *        200:
  *          description: The item was successfully deleted
  *          content:
  *            application/json:
  *              schema:
  *                $ref: "#/components/schemas/Attendance_sessions"
  *        400:
  *          description: Invalid ID supplied
  *        401:
  *          $ref: "#/components/responses/UnauthorizedError"
  *        404:
  *          description: Item not found
  *        500:
  *          description: Some server error
  */
router.delete('/:id', wrapAsync(async (req, res) => {
  await Attendance_sessionsService.remove(req.params.id, req.currentUser);
  const payload = true;
  res.status(200).send(payload);
}));

/**
  *  @swagger
  *  /api/attendance_sessions/deleteByIds:
  *    post:
  *      security:
  *        - bearerAuth: []
  *      tags: [Attendance_sessions]
  *      summary: Delete the selected item list
  *      description: Delete the selected item list
  *      requestBody:
  *        required: true
  *        content:
  *          application/json:
  *            schema:
  *              properties:
  *                ids:
  *                  description: IDs of the updated items
  *                  type: array
  *      responses:
  *        200:
  *          description: The items was successfully deleted
  *          content:
  *            application/json:
  *              schema:
  *                $ref: "#/components/schemas/Attendance_sessions"
  *        401:
  *          $ref: "#/components/responses/UnauthorizedError"
  *        404:
  *          description: Items not found
  *        500:
  *          description: Some server error
  */
router.post('/deleteByIds', wrapAsync(async (req, res) => {
    await Attendance_sessionsService.deleteByIds(req.body.data, req.currentUser);
    const payload = true;
    res.status(200).send(payload);
  }));

/**
  *  @swagger
  *  /api/attendance_sessions:
  *    get:
  *      security:
  *        - bearerAuth: []
  *      tags: [Attendance_sessions]
  *      summary: Get all attendance_sessions
  *      description: Get all attendance_sessions
  *      responses:
  *        200:
  *          description: Attendance_sessions list successfully received
  *          content:
  *            application/json:
  *              schema:
  *                type: array
  *                items:
  *                  $ref: "#/components/schemas/Attendance_sessions"
  *        401:
  *          $ref: "#/components/responses/UnauthorizedError"
  *        404:
  *          description: Data not found
  *        500:
  *          description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
  const filetype = req.query.filetype
  
  const currentUser = req.currentUser;
  const payload = await Attendance_sessionsDBApi.findAll(
    req.query,  { currentUser }
  );
  if (filetype && filetype === 'csv') {
    const fields = ['id','notes',
        
        
      'session_start','session_end',
        ];
    const opts = { fields };
    try {
      const csv = parse(payload.rows, opts);
      res.status(200).attachment(csv);
      res.send(csv)

    } catch (err) {
      console.error(err);
    }
  } else {
    res.status(200).send(payload);
  }

}));

/**
 *  @swagger
 *  /api/attendance_sessions/count:
 *    get:
 *      security:
 *        - bearerAuth: []
 *      tags: [Attendance_sessions]
 *      summary: Count all attendance_sessions
 *      description: Count all attendance_sessions
 *      responses:
 *        200:
 *          description: Attendance_sessions count successfully received
 *          content:
 *            application/json:
 *              schema:
 *                type: array
 *                items:
 *                  $ref: "#/components/schemas/Attendance_sessions"
 *        401:
 *          $ref: "#/components/responses/UnauthorizedError"
 *        404:
 *          description: Data not found
 *        500:
 *          description: Some server error
 */
router.get('/count', wrapAsync(async (req, res) => {
  
    const currentUser = req.currentUser;
    const payload = await Attendance_sessionsDBApi.findAll(
        req.query,
         null, 
        { countOnly: true, currentUser }
    );

    res.status(200).send(payload);
}));

/**
 *  @swagger
 *  /api/attendance_sessions/autocomplete:
 *    get:
 *      security:
 *        - bearerAuth: []
 *      tags: [Attendance_sessions]
 *      summary: Find all attendance_sessions that match search criteria
 *      description: Find all attendance_sessions that match search criteria
 *      responses:
 *        200:
 *          description: Attendance_sessions list successfully received
 *          content:
 *            application/json:
 *              schema:
 *                type: array
 *                items:
 *                  $ref: "#/components/schemas/Attendance_sessions"
 *        401:
 *          $ref: "#/components/responses/UnauthorizedError"
 *        404:
 *          description: Data not found
 *        500:
 *          description: Some server error
 */
router.get('/autocomplete', async (req, res) => {
  
  const payload = await Attendance_sessionsDBApi.findAllAutocomplete(
    req.query.query,
    req.query.limit,
    req.query.offset,
    
  );

  res.status(200).send(payload);
});

/**
  * @swagger
  *  /api/attendance_sessions/{id}:
  *    get:
  *      security:
  *        - bearerAuth: []
  *      tags: [Attendance_sessions]
  *      summary: Get selected item
  *      description: Get selected item
  *      parameters:
  *        - in: path
  *          name: id
  *          description: ID of item to get
  *          required: true
  *          schema:
  *            type: string
  *      responses:
  *        200:
  *          description: Selected item successfully received
  *          content:
  *            application/json:
  *              schema:
  *                $ref: "#/components/schemas/Attendance_sessions"
  *        400:
  *          description: Invalid ID supplied
  *        401:
  *          $ref: "#/components/responses/UnauthorizedError"
  *        404:
  *          description: Item not found
  *        500:
  *          description: Some server error
  */
router.get('/:id', wrapAsync(async (req, res) => {
  const payload = await Attendance_sessionsDBApi.findBy(
    { id: req.params.id },
  );
  
  

  res.status(200).send(payload);
}));

router.use('/', require('../helpers').commonErrorHandler);

module.exports = router;
