import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, MoreThanOrEqual } from 'typeorm';
import { Classroom, ClassroomStatus } from '../entities/classroom.entity';
import { ClassroomRental, ClassroomRentalStatus } from '../entities/classroom-rental.entity';
import { ClassSchedule } from '../entities/class-schedule.entity';
import { CreateClassroomDto, UpdateClassroomDto } from './dto/classroom.dto';

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

  async findAll(query?: { building?: string; roomType?: string; includeArchived?: boolean }) {
    const where: Record<string, unknown> = {};
    if (query?.building) where.building = query.building;
    if (query?.roomType) where.roomType = query.roomType;
    if (!query?.includeArchived) where.status = Not('archived');
    const list = await this.repo.find({ where, order: { building: 'ASC', name: 'ASC' } });
    const usageMap = await this.getUsageForClassrooms(list.map((c) => c.id));
    return list.map((classroom) => this.withEffectiveStatus(classroom, usageMap.get(classroom.id)));
  }

  async findOne(id: number) {
    const cls = await this.repo.findOne({ where: { id } });
    if (!cls) throw new NotFoundException('教室不存在');
    const usageMap = await this.getUsageForClassrooms([id]);
    return this.withEffectiveStatus(cls, usageMap.get(id));
  }

  async create(dto: CreateClassroomDto) {
    const exists = await this.repo.findOne({ where: { name: dto.name } });
    if (exists) throw new BadRequestException(`教室 ${dto.name} 已存在`);
    return this.repo.save(this.repo.create({ ...dto, status: ClassroomStatus.AVAILABLE }));
  }

  async update(id: number, dto: UpdateClassroomDto) {
    const classroom = await this.repo.findOne({ where: { id } });
    if (!classroom) throw new NotFoundException('教室不存在');
    if (dto.status === ClassroomStatus.MAINTENANCE && classroom.status !== dto.status) {
      await this.assertNoActiveAllocations(id);
    }
    await this.repo.update(id, dto);
    return this.repo.findOne({ where: { id } });
  }

  async remove(id: number) {
    const classroom = await this.repo.findOne({ where: { id } });
    if (!classroom) throw new NotFoundException('教室不存在');
    await this.assertNoActiveAllocations(id);
    await this.repo.update(id, { status: ClassroomStatus.ARCHIVED });
    return { message: '已归档' };
  }

  async restore(id: number) {
    const classroom = await this.repo.findOne({ where: { id } });
    if (!classroom) throw new NotFoundException('教室不存在');
    await this.repo.update(id, { status: ClassroomStatus.AVAILABLE });
    return this.repo.findOne({ where: { id } });
  }

  private withEffectiveStatus(
    classroom: Classroom,
    usage?: {
      state: 'in_use' | 'reserved';
      currentUsage: {
        type: 'schedule' | 'rental';
        title: string;
        startTime: string;
        endTime: string;
      } | null;
    },
  ) {
    const effectiveStatus =
      classroom.status === ClassroomStatus.ARCHIVED ||
      classroom.status === ClassroomStatus.MAINTENANCE
        ? classroom.status
        : (usage?.state ?? ClassroomStatus.AVAILABLE);
    return { ...classroom, currentUsage: usage?.currentUsage ?? null, effectiveStatus };
  }

  private async assertNoActiveAllocations(classroomId: number) {
    const today = new Intl.DateTimeFormat('en-CA', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(new Date());
    const scheduleCount = await this.scheduleRepo
      .createQueryBuilder('schedule')
      .where('schedule.classroomId = :classroomId', { classroomId })
      .andWhere('schedule.status = :active', { active: 'active' })
      .andWhere('schedule.endDate >= :today', { today })
      .getCount();
    const rentalCount = await this.rentalRepo.count({
      where: {
        classroomId,
        status: ClassroomRentalStatus.ACTIVE,
        endDate: MoreThanOrEqual(today),
      },
    });
    if (rentalCount > 0 || scheduleCount > 0) {
      throw new BadRequestException('该教室存在有效排课或租赁，无法维护或归档');
    }
  }

  private async getUsageForClassrooms(classroomIds: number[]): Promise<
    Map<
      number,
      {
        state: 'in_use' | 'reserved';
        currentUsage: {
          type: 'schedule' | 'rental';
          title: string;
          startTime: string;
          endTime: string;
        } | null;
      }
    >
  > {
    const result = new Map<
      number,
      {
        state: 'in_use' | 'reserved';
        currentUsage: {
          type: 'schedule' | 'rental';
          title: string;
          startTime: string;
          endTime: string;
        } | null;
      }
    >();
    if (classroomIds.length === 0) return result;

    const now = new Date();
    const todayStr = new Intl.DateTimeFormat('en-CA', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(now);
    const currentTime = new Intl.DateTimeFormat('en-GB', {
      timeZone: 'Asia/Shanghai',
      hour: '2-digit',
      minute: '2-digit',
      hour12: false,
    }).format(now);
    const shanghaiParts = new Intl.DateTimeFormat('en-US', {
      timeZone: 'Asia/Shanghai',
      weekday: 'short',
    }).format(now);
    const weekDayMap: Record<string, number> = {
      Mon: 1,
      Tue: 2,
      Wed: 3,
      Thu: 4,
      Fri: 5,
      Sat: 6,
      Sun: 7,
    };
    const weekDay = weekDayMap[shanghaiParts];

    const schedules = await this.scheduleRepo
      .createQueryBuilder('s')
      .leftJoin('Class', 'c', 'c.id = s.classId')
      .select('s.classroomId', 'classroomId')
      .addSelect('s.startTime', 'startTime')
      .addSelect('s.endTime', 'endTime')
      .addSelect('s.startDate', 'startDate')
      .addSelect('s.endDate', 'endDate')
      .addSelect('s.weekDay', 'weekDay')
      .addSelect('s.subject', 'subject')
      .addSelect('c.name', 'className')
      .where('s.classroomId IN (:...ids)', { ids: classroomIds })
      .andWhere('s.status = :active', { active: 'active' })
      .andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
      .andWhere('s.endDate >= :today', { today: todayStr })
      .getRawMany();

    for (const schedule of schedules) {
      const classroomId = Number(schedule.classroomId);
      const isCurrent =
        String(schedule.startDate) <= todayStr &&
        String(schedule.endDate) >= todayStr &&
        Number(schedule.weekDay) === weekDay &&
        String(schedule.startTime) <= currentTime &&
        String(schedule.endTime) >= currentTime;
      const existing = result.get(classroomId);
      if (!existing || isCurrent) {
        result.set(classroomId, {
          state: isCurrent ? 'in_use' : 'reserved',
          currentUsage: isCurrent
            ? {
                type: 'schedule',
                title: `${schedule.className || ''} ${schedule.subject || ''}`.trim() || '内部课程',
                startTime: String(schedule.startTime),
                endTime: String(schedule.endTime),
              }
            : null,
        });
      }
    }

    const rentals = await this.rentalRepo
      .createQueryBuilder('r')
      .leftJoin('Organization', 't', 't.id = r.lesseeOrganizationId')
      .select('r.classroomId', 'classroomId')
      .addSelect('r.startDate', 'startDate')
      .addSelect('r.endDate', 'endDate')
      .addSelect('t.name', 'tenantName')
      .where('r.classroomId IN (:...ids)', { ids: classroomIds })
      .andWhere('r.status = :active', { active: ClassroomRentalStatus.ACTIVE })
      .andWhere('r.endDate >= :today', { today: todayStr })
      .getRawMany();

    for (const rental of rentals) {
      const classroomId = Number(rental.classroomId);
      const isCurrent = String(rental.startDate) <= todayStr && String(rental.endDate) >= todayStr;
      const existing = result.get(classroomId);
      if (!existing || isCurrent) {
        result.set(classroomId, {
          state: isCurrent ? 'in_use' : 'reserved',
          currentUsage: isCurrent
            ? {
                type: 'rental',
                title: rental.tenantName ? `${rental.tenantName} 租赁` : '外部租赁',
                startTime: '00:00',
                endTime: '23:59',
              }
            : null,
        });
      }
    }

    return result;
  }

  async batchImport(
    rows: {
      name: string;
      building?: string;
      floor?: number;
      capacity?: number;
      roomType?: string;
    }[],
  ) {
    let imported = 0;
    let skipped = 0;
    const errors: string[] = [];
    for (const row of rows) {
      if (!row.name?.trim()) {
        skipped++;
        continue;
      }
      const exists = await this.repo.findOne({ where: { name: row.name.trim() } });
      if (exists) {
        errors.push(`教室 ${row.name} 已存在`);
        skipped++;
        continue;
      }
      await this.repo.save(this.repo.create({ ...row, capacity: row.capacity || 30 }));
      imported++;
    }
    return {
      message: `成功导入 ${imported} 间教室，跳过 ${skipped} 间`,
      imported,
      skipped,
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  async getUsageReport(dateFrom: string, dateTo: string) {
    const classrooms = await this.repo.find({
      where: { status: Not(ClassroomStatus.ARCHIVED) },
      order: { building: 'ASC', name: 'ASC' },
    });

    const rentals = await this.rentalRepo
      .createQueryBuilder('r')
      .where('r.status IN (:...statuses)', {
        statuses: [ClassroomRentalStatus.ACTIVE, ClassroomRentalStatus.ENDED],
      })
      .andWhere('r.startDate <= :dateTo AND r.endDate >= :dateFrom', { dateFrom, dateTo })
      .getMany();

    const schedules = await this.scheduleRepo
      .createQueryBuilder('s')
      .where('s.status = :active', { active: 'active' })
      .andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
      .andWhere('s.startDate <= :dateTo AND s.endDate >= :dateFrom', { dateFrom, dateTo })
      .getMany();

    const start = new Date(dateFrom);
    const end = new Date(dateTo);
    const totalDays = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1;

    const rentalDaysByRoom: Record<number, Set<string>> = {};
    const scheduleDaysByRoom: Record<number, Set<string>> = {};

    for (const r of rentals) {
      if (!rentalDaysByRoom[r.classroomId]) rentalDaysByRoom[r.classroomId] = new Set();
      const effStart = new Date(Math.max(new Date(r.startDate).getTime(), start.getTime()));
      const effEnd = new Date(Math.min(new Date(r.endDate).getTime(), end.getTime()));
      for (let d = new Date(effStart); d <= effEnd; d.setDate(d.getDate() + 1)) {
        rentalDaysByRoom[r.classroomId].add(d.toISOString().slice(0, 10));
      }
    }

    for (const s of schedules) {
      if (!scheduleDaysByRoom[s.classroomId]) scheduleDaysByRoom[s.classroomId] = new Set();
      const effStart = new Date(Math.max(new Date(s.startDate).getTime(), start.getTime()));
      const effEnd = new Date(Math.min(new Date(s.endDate).getTime(), end.getTime()));
      for (let d = new Date(effStart); d <= effEnd; d.setDate(d.getDate() + 1)) {
        scheduleDaysByRoom[s.classroomId].add(d.toISOString().slice(0, 10));
      }
    }

    return classrooms.map((c) => {
      const rentalDays = rentalDaysByRoom[c.id]?.size || 0;
      const scheduleDays = scheduleDaysByRoom[c.id]?.size || 0;
      const usedDays = rentalDays + scheduleDays;
      return {
        id: c.id,
        name: c.name,
        building: c.building || '',
        roomType: c.roomType || '',
        capacity: c.capacity,
        totalDays,
        rentalDays,
        scheduleDays,
        usedDays,
        idleDays: totalDays - usedDays,
        occupancyRate: totalDays > 0 ? ((usedDays / totalDays) * 100).toFixed(1) : '0.0',
      };
    });
  }
}
