import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import { AttendanceRecord, ClassSchedule } from '../entities';
import type { AttendanceCalendarQueryDto } from './dto/attendance.dto';

@Injectable()
export class AttendanceCalendarService {
  constructor(
    @InjectRepository(AttendanceRecord)
    private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(ClassSchedule)
    private scheduleRepo: Repository<ClassSchedule>,
  ) {}

  async getCalendar(query: AttendanceCalendarQueryDto) {
    const { classId, weekStart } = query;

    if (!weekStart) {
      // Default to the Monday of the current week
      const now = new Date();
      const day = now.getDay();
      const diff = day === 0 ? -6 : 1 - day; // Monday offset
      const monday = new Date(now);
      monday.setDate(now.getDate() + diff);
      const mondayStr = monday.toISOString().slice(0, 10);

      return this.buildCalendar(classId, mondayStr);
    }

    return this.buildCalendar(classId, weekStart);
  }

  private getWeekDayForDate(date: string): number {
    const day = new Date(`${date}T00:00:00+08:00`).getUTCDay();
    return day === 0 ? 7 : day;
  }

  async getScheduleOptionsForAttendance(classId: number, date: string) {
    const weekDay = this.getWeekDayForDate(date);
    const { entities, raw } = await this.scheduleRepo
      .createQueryBuilder('cs')
      .leftJoin('cs.teacher', 'teacher')
      .addSelect('cs.id', 'scheduleIdForTeacherMap')
      .addSelect('teacher.username', 'teacherUsername')
      .addSelect('teacher.name', 'teacherName')
      .where('cs.classId = :classId', { classId })
      .andWhere('cs.weekDay = :weekDay', { weekDay })
      .andWhere('cs.startDate <= :date', { date })
      .andWhere('cs.endDate >= :date', { date })
      .andWhere('cs.status = :status', { status: 'active' })
      .orderBy('cs.startTime', 'ASC')
      .addOrderBy('cs.subject', 'ASC')
      .getRawAndEntities();

    const teacherByScheduleId = new Map(
      raw.map((row: { scheduleIdForTeacherMap: string; teacherName: string | null; teacherUsername: string | null }) => [
        Number(row.scheduleIdForTeacherMap),
        {
          teacherName: row.teacherName || null,
          teacherUsername: row.teacherUsername || null,
        },
      ]),
    );

    return entities.map((schedule) => {
      const teacher = teacherByScheduleId.get(schedule.id) ?? {
        teacherName: null,
        teacherUsername: null,
      };
      return { ...schedule, ...teacher };
    });
  }

  private async buildCalendar(classId: number, weekStart: string) {
    // Compute weekEnd (Sunday = weekStart + 6 days)
    const start = new Date(weekStart);
    const end = new Date(start);
    end.setDate(start.getDate() + 6);
    const endStr = end.toISOString().slice(0, 10);

    const records = await this.attendanceRepo.find({
      where: {
        classId,
        attendanceDate: Between(weekStart, endStr),
      },
      relations: ['student'],
      order: { attendanceDate: 'ASC', session: 'ASC' },
    });

    // Group by studentId
    const studentMap = new Map<
      number,
      {
        studentId: number;
        studentName: string;
        days: Array<{ date: string; session: string; status: string }>;
      }
    >();

    for (const r of records) {
      if (!studentMap.has(r.studentId)) {
        studentMap.set(r.studentId, {
          studentId: r.studentId,
          studentName: r.student?.name ?? `Student#${r.studentId}`,
          days: [],
        });
      }
      studentMap.get(r.studentId)!.days.push({
        date: r.attendanceDate,
        session: r.session,
        status: r.status,
      });
    }

    return Array.from(studentMap.values());
  }
}
