import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClassSchedule } from '../entities';
import type { WeeklyViewQueryDto } from './dto/schedule.dto';

const ACTIVE_SCHEDULE_STATUS = 'active';

@Injectable()
export class ScheduleQueriesService {
  constructor(
    @InjectRepository(ClassSchedule)
    private readonly scheduleRepo: Repository<ClassSchedule>,
  ) {}

  maskScheduleOccupancy(schedule: ClassSchedule) {
    return {
      id: null,
      classId: null,
      classroomId: schedule.classroomId,
      weekDay: schedule.weekDay,
      startTime: schedule.startTime,
      endTime: schedule.endTime,
      attendanceAdvanceMinutes: schedule.attendanceAdvanceMinutes,
      startDate: schedule.startDate,
      endDate: schedule.endDate,
      subject: '已占用',
      teacherId: null,
      scheduleType: schedule.scheduleType,
      status: schedule.status,
      notes: null,
      canViewDetails: false,
    };
  }


  async agentSearchSchedules(
    accessibleClassIds: number[] | undefined,
    query?: { classId?: number; classroomId?: number; weekDay?: number; limit?: number },
  ): Promise<
    {
      id: number;
      classId: number | null;
      className: string | null;
      classroomId: number;
      classroomName: string | null;
      weekDay: number;
      startTime: string;
      endTime: string;
      subject: string;
      teacherName: string | null;
      startDate: string;
      endDate: string;
      scheduleType: string;
      status: string;
    }[]
  > {
    if (query?.classId && accessibleClassIds && !accessibleClassIds.includes(query.classId)) {
      return [];
    }
    if (accessibleClassIds && accessibleClassIds.length === 0) {
      return [];
    }
    const qb = this.scheduleRepo
      .createQueryBuilder('cs')
      .leftJoin('cs.class', 'class')
      .leftJoin('cs.classroom', 'classroom')
      .leftJoin('cs.teacher', 'teacher')
      .select([
        'cs.id',
        'cs.classId',
        'cs.classroomId',
        'cs.weekDay',
        'cs.startTime',
        'cs.endTime',
        'cs.subject',
        'cs.teacherId',
        'cs.startDate',
        'cs.endDate',
        'cs.scheduleType',
        'cs.status',
        'class.name',
        'classroom.name',
        'teacher.name',
      ])
      .where('cs.status = :active', { active: 'active' });

    if (query?.classroomId) {
      qb.andWhere('cs.classroomId = :classroomId', { classroomId: query.classroomId });
    }
    if (query?.classId) {
      qb.andWhere('cs.classId = :classId', { classId: query.classId });
    }
    if (accessibleClassIds) {
      qb.andWhere('cs.classId IN (:...accessibleClassIds)', { accessibleClassIds });
    }
    if (query?.weekDay) {
      qb.andWhere('cs.weekDay = :weekDay', { weekDay: query.weekDay });
    }

    const rows = await qb
      .orderBy('cs.weekDay', 'ASC')
      .addOrderBy('cs.startTime', 'ASC')
      .limit(Math.max(1, Math.min(query?.limit ?? 20, 50)))
      .getRawMany<Record<string, unknown>>();
    return rows.map((row) => ({
      id: Number(row.cs_id),
      classId: row.cs_class_id == null ? null : Number(row.cs_class_id),
      className: row.class_name == null ? null : String(row.class_name),
      classroomId: Number(row.cs_classroom_id),
      classroomName: row.classroom_name == null ? null : String(row.classroom_name),
      weekDay: Number(row.cs_week_day),
      startTime: String(row.cs_start_time),
      endTime: String(row.cs_end_time),
      subject: String(row.cs_subject),
      teacherName: row.teacher_name == null ? null : String(row.teacher_name),
      startDate: String(row.cs_start_date),
      endDate: String(row.cs_end_date),
      scheduleType: String(row.cs_schedule_type),
      status: String(row.cs_status),
    }));
  }


  async getWeeklyView(query: WeeklyViewQueryDto, accessibleClassIds?: number[]) {
    const qb = this.scheduleRepo.createQueryBuilder('cs');
    if (query.classroomId) {
      qb.andWhere('cs.classroomId = :classroomId', { classroomId: query.classroomId });
    }
    if (query.startDate) {
      qb.andWhere('cs.endDate >= :startDate', { startDate: query.startDate });
    }
    if (query.endDate) {
      qb.andWhere('cs.startDate <= :endDate', { endDate: query.endDate });
    }

    const schedules = await qb
      .andWhere('cs.status = :status', { status: ACTIVE_SCHEDULE_STATUS })
      .orderBy('cs.weekDay', 'ASC')
      .addOrderBy('cs.startTime', 'ASC')
      .getMany();

    const allowedClassIds = accessibleClassIds ? new Set(accessibleClassIds) : null;
    const visibleSchedules = schedules.map((schedule) => {
      const canViewDetails =
        allowedClassIds === null ||
        (schedule.classId !== null && allowedClassIds.has(schedule.classId));
      if (canViewDetails) return { ...schedule, canViewDetails: true };

      // Other classes remain visible only as a room/time occupancy block.
      // Do not expose class, subject, teacher, notes, or internal record IDs.
      return this.maskScheduleOccupancy(schedule);
    });

    // Group by classroomId → weekDay
    const matrix: Record<number, Record<number, typeof visibleSchedules>> = {};
    for (const schedule of visibleSchedules) {
      if (!matrix[schedule.classroomId]) matrix[schedule.classroomId] = {};
      if (!matrix[schedule.classroomId][schedule.weekDay])
        matrix[schedule.classroomId][schedule.weekDay] = [];
      matrix[schedule.classroomId][schedule.weekDay].push(schedule);
    }

    return matrix;
  }


  async getClassroomOccupancy(classroomId: number, date?: string) {
    const qb = this.scheduleRepo
      .createQueryBuilder('cs')
      .where('cs.classroomId = :classroomId', { classroomId })
      .andWhere('cs.status = :status', { status: ACTIVE_SCHEDULE_STATUS })
      .andWhere('cs.scheduleType IN (:...scheduleTypes)', {
        scheduleTypes: ['INTERNAL', 'RENTAL'],
      });

    if (date) {
      qb.andWhere('cs.startDate <= :date', { date }).andWhere('cs.endDate >= :date', { date });
    }

    return qb.orderBy('cs.weekDay', 'ASC').addOrderBy('cs.startTime', 'ASC').getMany();
  }
}
