import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { ClassroomRental, Classroom, ClassSchedule, ClassroomStatus } from '../entities';
import { ClassroomRentalStatus } from '../entities/classroom-rental.entity';

const COLOR_PALETTE = [
  "#5B8FF9",
  "#61DDAA",
  "#65789B",
  "#F6BD16",
  "#7262FD",
  "#78D3F8",
  "#9661BC",
  "#F6903D",
  "#008685",
  "#F08BB4"
];

@Injectable()
export class RentalScheduleService {
  constructor(
    @InjectRepository(ClassroomRental) private repo: Repository<ClassroomRental>,
    @InjectRepository(Classroom) private classroomRepo: Repository<Classroom>,
    @InjectRepository(ClassSchedule) private scheduleRepo: Repository<ClassSchedule>,
  ) {}

  async getUnavailableDates(classroomId: number, year: number, month: number, excludeId?: number) {
    const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
    const monthStart = `${year}-${String(month).padStart(2, '0')}-01`;
    const monthEnd = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;

    const [rentals, schedules] = await Promise.all([
      this.repo.find({
        where: {
          ...(excludeId ? { id: Not(excludeId) } : {}),
          classroomId,
          status: ClassroomRentalStatus.ACTIVE,
          startDate: LessThanOrEqual(monthEnd),
          endDate: MoreThanOrEqual(monthStart),
        },
      }),
      this.scheduleRepo.find({
        where: {
          classroomId,
          status: ClassroomRentalStatus.ACTIVE,
          scheduleType: 'INTERNAL',
          startDate: LessThanOrEqual(monthEnd),
          endDate: MoreThanOrEqual(monthStart),
        },
      }),
    ]);

    const unavailableDates = new Set<string>();
    for (const rental of rentals) {
      this.addDateRange(
        unavailableDates,
        rental.startDate > monthStart ? rental.startDate : monthStart,
        rental.endDate < monthEnd ? rental.endDate : monthEnd,
      );
    }
    for (const schedule of schedules) {
      this.addScheduleOccurrences(unavailableDates, schedule, monthStart, monthEnd);
    }

    return { dates: Array.from(unavailableDates).sort() };
  }

  /**
   * 查找与给定区间冲突的租赁订单，同时检测同一教室同一日期段的内部排课
   * 重叠判定：start1 <= end2 AND start2 <= end1
   */
  async findConflicts(classroomId: number, startDate: string, endDate: string, excludeId?: number) {
    const qb = this.repo
      .createQueryBuilder('r')
      .leftJoinAndSelect('r.lesseeOrganization', 'lesseeOrganization')
      .where('r.classroomId = :cid', { cid: classroomId })
      .andWhere('r.status = :active', { active: ClassroomRentalStatus.ACTIVE })
      .andWhere('r.startDate <= :end', { end: endDate })
      .andWhere('r.endDate >= :start', { start: startDate });
    if (excludeId) qb.andWhere('r.id != :excludeId', { excludeId });
    const rentals = await qb.getMany();

    // 检测同一教室同一日期段是否存在内部排课
    const scheduleCandidates = await this.scheduleRepo
      .createQueryBuilder('cs')
      .where('cs.classroomId = :cid', { cid: classroomId })
      .andWhere('cs.status = :status', { status: 'active' })
      .andWhere('cs.scheduleType = :scheduleType', { scheduleType: 'INTERNAL' })
      .andWhere('cs.startDate <= :end', { end: endDate })
      .andWhere('cs.endDate >= :start', { start: startDate })
      .getMany();
    const scheduleConflicts = scheduleCandidates.filter((schedule) =>
      this.hasScheduleOccurrence(schedule, startDate, endDate),
    );

    if (scheduleConflicts.length > 0) {
      throw new ConflictException({
        message: '该教室在此时间段已有排课',
        conflicts: scheduleConflicts.map((s) => ({
          id: s.id,
          startDate: s.startDate,
          endDate: s.endDate,
          organizationName: `[内部排课] ${s.subject}`,
        })),
      });
    }

    return rentals;
  }

  private hasScheduleOccurrence(
    schedule: ClassSchedule,
    startDate: string,
    endDate: string,
  ): boolean {
    const overlapStart = schedule.startDate > startDate ? schedule.startDate : startDate;
    const overlapEnd = schedule.endDate < endDate ? schedule.endDate : endDate;
    if (overlapStart > overlapEnd) return false;

    const startUtc = this.toUtcDate(overlapStart);
    const endUtc = this.toUtcDate(overlapEnd);
    const startWeekDay = startUtc.getUTCDay() || 7;
    const daysUntilOccurrence = (schedule.weekDay - startWeekDay + 7) % 7;
    startUtc.setUTCDate(startUtc.getUTCDate() + daysUntilOccurrence);
    return startUtc <= endUtc;
  }

  private toUtcDate(date: string): Date {
    const [year, month, day] = date.split('-').map(Number);
    return new Date(Date.UTC(year, month - 1, day));
  }

  private addDateRange(dates: Set<string>, startDate: string, endDate: string) {
    const current = this.toUtcDate(startDate);
    const end = this.toUtcDate(endDate);
    while (current <= end) {
      dates.add(current.toISOString().slice(0, 10));
      current.setUTCDate(current.getUTCDate() + 1);
    }
  }

  private addScheduleOccurrences(
    dates: Set<string>,
    schedule: ClassSchedule,
    startDate: string,
    endDate: string,
  ) {
    const overlapStart = schedule.startDate > startDate ? schedule.startDate : startDate;
    const overlapEnd = schedule.endDate < endDate ? schedule.endDate : endDate;
    if (overlapStart > overlapEnd) return;

    const current = this.toUtcDate(overlapStart);
    const end = this.toUtcDate(overlapEnd);
    const startWeekDay = current.getUTCDay() || 7;
    current.setUTCDate(current.getUTCDate() + ((schedule.weekDay - startWeekDay + 7) % 7));
    while (current <= end) {
      dates.add(current.toISOString().slice(0, 10));
      current.setUTCDate(current.getUTCDate() + 7);
    }
  }


  async getSchedule(year: number, month: number) {
    const lastDay = new Date(year, month, 0).getDate();
    const first = `${year}-${String(month).padStart(2, '0')}-01`;
    const last = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;

    const classrooms = await this.classroomRepo.find({
      where: { status: Not(ClassroomStatus.ARCHIVED) },
      order: { building: 'ASC', name: 'ASC' },
    });
    const rentals = await this.repo
      .createQueryBuilder('r')
      .leftJoinAndSelect('r.lesseeOrganization', 'lesseeOrganization')
      .leftJoinAndSelect('r.classroom', 'classroom')
      .where('r.status IN (:...statuses)', {
        statuses: [ClassroomRentalStatus.ACTIVE, ClassroomRentalStatus.ENDED],
      })
      .andWhere('r.startDate <= :last AND r.endDate >= :first', { first, last })
      .getMany();

    const organizationMap = new Map<number, any>();
    const matrix: Record<number, Record<number, any>> = {};
    const summary: Record<
      number,
      { totalDays: number; rentedDays: number; idleDays: number; occupancyRate: number }
    > = {};

    for (const cls of classrooms) {
      matrix[cls.id] = {};
      summary[cls.id] = { totalDays: lastDay, rentedDays: 0, idleDays: lastDay, occupancyRate: 0 };
    }

    for (const rental of rentals) {
      const start = new Date(rental.startDate);
      const end = new Date(rental.endDate);
      const monthStart = new Date(first);
      const monthEnd = new Date(last);
      const effStart = start < monthStart ? monthStart : start;
      const effEnd = end > monthEnd ? monthEnd : end;
      if (rental.lesseeOrganization && !organizationMap.has(rental.lesseeOrganization.id)) {
        organizationMap.set(rental.lesseeOrganization.id, {
          id: rental.lesseeOrganization.id,
          name: rental.lesseeOrganization.name,
          color:
            rental.lesseeOrganization.color ||
            COLOR_PALETTE[rental.lesseeOrganization.id % COLOR_PALETTE.length],
        });
      }
      for (let d = new Date(effStart); d <= effEnd; d.setDate(d.getDate() + 1)) {
        const day = d.getDate();
        if (!matrix[rental.classroomId]) continue;
        matrix[rental.classroomId][day] = {
          scheduleType: 'RENTAL',
          rentalId: rental.id,
          organizationId: rental.lesseeOrganizationId,
          organizationName: rental.lesseeOrganization?.name || '未知',
          color:
            rental.lesseeOrganization?.color ||
            COLOR_PALETTE[(rental.lesseeOrganizationId || 0) % COLOR_PALETTE.length],
          hasContract: !!rental.contractPath,
        };
      }
    }

    // ── Overlay internal class schedules ──
    const schedules = await this.scheduleRepo
      .createQueryBuilder('s')
      .leftJoinAndSelect('s.class', 'class')
      .leftJoinAndSelect('s.teacher', 'teacher')
      .where('s.status = :active', { active: 'active' })
      .andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
      .andWhere('s.startDate <= :last AND s.endDate >= :first', { first, last })
      .getMany();

    for (const sched of schedules) {
      if (!sched.classroomId) continue;
      const schedStart = new Date(
        Math.max(new Date(sched.startDate).getTime(), new Date(first).getTime()),
      );
      const schedEnd = new Date(
        Math.min(new Date(sched.endDate).getTime(), new Date(last).getTime()),
      );
      for (let d = new Date(schedStart); d <= schedEnd; d.setDate(d.getDate() + 1)) {
        const dow = d.getDay() === 0 ? 7 : d.getDay();
        if (dow !== sched.weekDay) continue;
        const day = d.getDate();
        if (!matrix[sched.classroomId]) continue;
        matrix[sched.classroomId][day] = {
          scheduleType: 'INTERNAL',
          scheduleId: sched.id,
          className: (sched.class as { name?: string } | null)?.name || '',
          subject: sched.subject,
          teacherName: (sched.teacher as { name?: string } | null)?.name || '',
          startTime: sched.startTime,
          endTime: sched.endTime,
          color: '#52c41a',
        };
      }
    }
    // 统计
    for (const cls of classrooms) {
      const rented = Object.keys(matrix[cls.id]).length;
      summary[cls.id].rentedDays = rented;
      summary[cls.id].idleDays = lastDay - rented;
      summary[cls.id].occupancyRate = lastDay > 0 ? Math.round((rented / lastDay) * 100) / 100 : 0;
    }

    return {
      year,
      month,
      days: lastDay,
      classrooms: classrooms.map((c) => ({
        id: c.id,
        name: c.name,
        building: c.building,
        floor: c.floor,
        roomType: c.roomType,
        capacity: c.capacity,
      })),
      organizations: Array.from(organizationMap.values()),
      matrix,
      summary,
    };
  }
  withEffectiveStatus(rental: ClassroomRental) {
    const today = new Intl.DateTimeFormat('en-CA', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(new Date());
    const effectiveStatus =
      rental.status === ClassroomRentalStatus.ACTIVE && rental.endDate < today
        ? ClassroomRentalStatus.ENDED
        : rental.status;
    return Object.assign(rental, { effectiveStatus });
  }

  /**
   * 同步租赁订单到 class_schedules（schedule_type = 'RENTAL'）
   */

  async syncScheduleFromRental(rental: ClassroomRental, organizationName?: string) {
    const name = organizationName || rental.lesseeOrganization?.name || '承租机构';
    const weekDay = this.dateToWeekDay(rental.startDate);
    let schedule = await this.scheduleRepo.findOne({
      where: { rentalId: rental.id, scheduleType: 'RENTAL' },
    });
    const data = {
      classroomId: rental.classroomId,
      classId: null,
      weekDay,
      startTime: '00:00',
      endTime: '23:59',
      startDate: rental.startDate,
      endDate: rental.endDate,
      subject: `${name} 租赁`,
      teacherId: null,
      scheduleType: 'RENTAL',
      rentalId: rental.id,
      status: rental.status === ClassroomRentalStatus.CANCELLED ? 'cancelled' : 'active',
      notes: rental.notes,
    };
    if (schedule) {
      await this.scheduleRepo.update(schedule.id, data);
    } else {
      schedule = this.scheduleRepo.create(data);
      await this.scheduleRepo.save(schedule);
    }
  }


  dateToWeekDay(date: string): number {
    const d = new Date(date);
    const day = d.getDay();
    return day === 0 ? 7 : day;
  }

}
