import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
  Repository,
  DataSource,
  IsNull,
  Between,
  LessThanOrEqual,
  MoreThanOrEqual,
  In,
  SelectQueryBuilder,
  ObjectLiteral,
} from 'typeorm';
import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { Deposit } from '../entities/deposit.entity';
import { Organization } from '../entities/organization.entity';
import { CheckInDto, CheckOutDto, TransferRoomDto } from './dto/occupancy.dto';
import { RoomsService } from '../rooms/rooms.service';

class ImportRowSkipped extends Error {}

@Injectable()
export class OccupanciesService {
  constructor(
    @InjectRepository(Occupancy) private repo: Repository<Occupancy>,
    @InjectRepository(Room) private roomRepo: Repository<Room>,
    @InjectRepository(Student) private studentRepo: Repository<Student>,
    @InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
    @InjectRepository(Bed) private bedRepo: Repository<Bed>,
    @InjectRepository(Locker) private lockerRepo: Repository<Locker>,
    @InjectRepository(Organization) private organizationRepo: Repository<Organization>,
    private dataSource: DataSource,
  ) {}

  private withPessimisticWriteLock<T extends ObjectLiteral>(
    qb: SelectQueryBuilder<T>,
  ): SelectQueryBuilder<T> {
    const type = this.dataSource.options.type;
    if (type === 'mysql' || type === 'mariadb' || type === 'postgres' || type === 'cockroachdb') {
      return qb.setLock('pessimistic_write');
    }
    return qb;
  }

  async findAll(query?: { roomId?: number; studentId?: number; active?: boolean }) {
    const qb = this.repo
      .createQueryBuilder('o')
      .leftJoinAndSelect('o.student', 'student')
      .leftJoinAndSelect('o.room', 'room')
      .leftJoinAndSelect('o.bed', 'bed')
      .leftJoinAndSelect('o.locker', 'locker')
      .where('o.status = :status', { status: 'active' })
      .orderBy('o.checkInDate', 'DESC');
    if (query?.roomId) qb.andWhere('o.roomId = :roomId', { roomId: query.roomId });
    if (query?.studentId) qb.andWhere('o.studentId = :studentId', { studentId: query.studentId });
    if (query?.active) qb.andWhere('o.checkOutDate IS NULL');
    return qb.getMany();
  }

  async checkIn(dto: CheckInDto, userId?: number) {
    this.assertDateOrder(dto.checkInDate, dto.billingStartDate, '计费起始日不能早于入住日期');
    return this.dataSource.transaction(async (manager) => {
      const existing = await this.withPessimisticWriteLock(
        manager
          .createQueryBuilder(Occupancy, 'occupancy')
          .where('occupancy.studentId = :studentId', { studentId: dto.studentId })
          .andWhere('occupancy.checkOutDate IS NULL'),
      ).getOne();
      if (existing) throw new BadRequestException('该学生已有在住记录，请先办理退宿');

      const room = await this.withPessimisticWriteLock(
        manager.createQueryBuilder(Room, 'room').where('room.id = :roomId', { roomId: dto.roomId }),
      ).getOne();
      if (!room) throw new NotFoundException('宿舍不存在');
      if (room.status === 'archived' || room.status === 'maintenance') {
        throw new BadRequestException('该宿舍当前不可入住');
      }
      const count = await manager.count(Occupancy, {
        where: { roomId: dto.roomId, checkOutDate: IsNull() },
      });
      if (count >= (room.capacity ?? 0)) throw new BadRequestException('宿舍已满');
      const student = await manager.findOne(Student, { where: { id: dto.studentId } });
      if (!student) throw new NotFoundException('学生不存在');

      if (dto.bedId) {
        const bed = await this.withPessimisticWriteLock(
          manager.createQueryBuilder(Bed, 'bed').where('bed.id = :bedId AND bed.roomId = :roomId', {
            bedId: dto.bedId,
            roomId: dto.roomId,
          }),
        ).getOne();
        if (!bed) throw new BadRequestException('床位不存在或不属于该宿舍');
        if (bed.status !== 'available') throw new BadRequestException('该床位已被占用或维修中');
      }
      if (dto.lockerId) {
        const locker = await this.withPessimisticWriteLock(
          manager
            .createQueryBuilder(Locker, 'locker')
            .where('locker.id = :lockerId AND locker.roomId = :roomId', {
              lockerId: dto.lockerId,
              roomId: dto.roomId,
            }),
        ).getOne();
        if (!locker) throw new BadRequestException('柜子不存在或不属于该宿舍');
        if (locker.status !== 'available') throw new BadRequestException('柜子已被占用或维修中');
      }

      const saved = await manager.save(
        manager.create(Occupancy, {
          studentId: dto.studentId,
          roomId: dto.roomId,
          checkInDate: dto.checkInDate,
          billingStartDate: dto.billingStartDate || dto.checkInDate,
          stayType: dto.stayType,
          responsibleOrganizationId: student.organizationId,
          notes: dto.notes,
          bedId: dto.bedId,
          lockerId: dto.lockerId,
        }),
      );
      if (dto.bedId) await manager.update(Bed, dto.bedId, { status: 'occupied' });
      if (dto.lockerId) await manager.update(Locker, dto.lockerId, { status: 'occupied' });
      if (count + 1 >= (room.capacity ?? 0)) await manager.update(Room, room.id, { status: 'full' });
      if (dto.collectDeposit) {
        let deposit = await manager.findOne(Deposit, { where: { studentId: dto.studentId } });
        if (deposit) {
          deposit.amount = Number(
            (Number(deposit.amount || 0) + Number(dto.depositAmount ?? 500)).toFixed(2),
          );
          deposit.status = 'paid';
          deposit.paidDate = dto.checkInDate;
          deposit.recordedBy = userId ?? null;
          deposit.notes = '入住登记自动收取';
        } else {
          deposit = manager.create(Deposit, {
            studentId: dto.studentId,
            amount: dto.depositAmount ?? 500,
            paidDate: dto.checkInDate,
            status: 'paid',
            recordedBy: userId,
            notes: '入住登记自动收取',
          });
        }
        await manager.save(deposit);
      }
      return saved;
    });
  }

  async checkOut(occupancyId: number, dto: CheckOutDto) {
    return this.dataSource.transaction(async (manager) => {
      const occ = await this.withPessimisticWriteLock(
        manager
          .createQueryBuilder(Occupancy, 'occupancy')
          .where('occupancy.id = :id', { id: occupancyId }),
      ).getOne();
      if (!occ) throw new NotFoundException('入住记录不存在');
      if (occ.checkOutDate) throw new BadRequestException('该记录已退宿');
      this.assertDateOrder(occ.checkInDate, dto.checkOutDate, '退宿日期不能早于入住日期');
      this.assertDateOrder(
        occ.billingStartDate || occ.checkInDate,
        dto.billingEndDate || dto.checkOutDate,
        '计费截止日不能早于计费起始日',
      );
      occ.checkOutDate = dto.checkOutDate;
      occ.billingEndDate = dto.billingEndDate || dto.checkOutDate;
      occ.checkOutReason = dto.checkOutReason || '';
      await manager.save(occ);
      if (occ.bedId) await manager.update(Bed, occ.bedId, { status: 'available' });
      if (occ.lockerId) await manager.update(Locker, occ.lockerId, { status: 'available' });
      await manager.update(Room, occ.roomId, { status: 'available' });
      return occ;
    });
  }

  async transferRoom(occupancyId: number, dto: TransferRoomDto) {
    const runner = this.dataSource.createQueryRunner();
    await runner.connect();
    await runner.startTransaction();
    try {
      const oldOcc = await this.withPessimisticWriteLock(
        runner.manager
          .createQueryBuilder(Occupancy, 'occupancy')
          .where('occupancy.id = :id', { id: occupancyId }),
      ).getOne();
      if (!oldOcc) throw new NotFoundException('入住记录不存在');
      if (oldOcc.checkOutDate) throw new BadRequestException('该记录已退宿');
      if (oldOcc.roomId === dto.newRoomId)
        throw new BadRequestException('目标宿舍不能与当前宿舍相同');
      this.assertDateOrder(oldOcc.checkInDate, dto.transferDate, '换房日期不能早于原入住日期');
      this.assertDateOrder(
        oldOcc.billingStartDate || oldOcc.checkInDate,
        dto.oldBillingEndDate || dto.transferDate,
        '原宿舍计费截止日不能早于计费起始日',
      );

      // 退旧房
      oldOcc.checkOutDate = dto.transferDate;
      oldOcc.billingEndDate = dto.oldBillingEndDate || dto.transferDate;
      oldOcc.checkOutReason = dto.reason || '换房';
      await runner.manager.save(oldOcc);
      // 释放旧床位/柜子
      if (oldOcc.bedId) {
        await runner.manager.update(Bed, oldOcc.bedId, { status: 'available' });
      }
      if (oldOcc.lockerId) {
        await runner.manager.update(Locker, oldOcc.lockerId, { status: 'available' });
      }
      await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
      // 检查新房容量
      const newRoom = await this.withPessimisticWriteLock(
        runner.manager
          .createQueryBuilder(Room, 'room')
          .where('room.id = :roomId', { roomId: dto.newRoomId }),
      ).getOne();
      if (!newRoom) throw new NotFoundException('目标宿舍不存在');
      if (newRoom.status === 'archived' || newRoom.status === 'maintenance') {
        throw new BadRequestException('目标宿舍当前不可入住');
      }
      const count = await runner.manager.count(Occupancy, {
        where: { roomId: dto.newRoomId, checkOutDate: IsNull() },
      });
      if (count >= (newRoom.capacity ?? 0)) throw new BadRequestException('目标宿舍已满');

      // 新床位校验
      if (dto.newBedId) {
        const newBed = await this.withPessimisticWriteLock(
          runner.manager
            .createQueryBuilder(Bed, 'bed')
            .where('bed.id = :bedId AND bed.roomId = :roomId', {
              bedId: dto.newBedId,
              roomId: dto.newRoomId,
            }),
        ).getOne();
        if (!newBed) throw new BadRequestException('目标床位不存在或不属于目标宿舍');
        if (newBed.status !== 'available') throw new BadRequestException('目标床位已被占用');
      }
      if (dto.newLockerId) {
        const newLocker = await this.withPessimisticWriteLock(
          runner.manager
            .createQueryBuilder(Locker, 'locker')
            .where('locker.id = :lockerId AND locker.roomId = :roomId', {
              lockerId: dto.newLockerId,
              roomId: dto.newRoomId,
            }),
        ).getOne();
        if (!newLocker) throw new BadRequestException('目标柜子不存在或不属于目标宿舍');
        if (newLocker.status !== 'available') throw new BadRequestException('目标柜子已被占用');
      }

      // 计算新房计费起始日：默认为换房日期次日
      const transferDate = new Date(dto.transferDate);
      const nextDay = new Date(transferDate);
      nextDay.setDate(nextDay.getDate() + 1);
      const defaultBillingStart = nextDay.toISOString().split('T')[0];
      this.assertDateOrder(
        dto.transferDate,
        dto.newBillingStartDate || defaultBillingStart,
        '新宿舍计费起始日不能早于换房日期',
      );

      // 入住新房
      const newOcc = runner.manager.create(Occupancy, {
        studentId: oldOcc.studentId,
        roomId: dto.newRoomId,
        checkInDate: dto.transferDate,
        billingStartDate: dto.newBillingStartDate || defaultBillingStart,
        stayType: oldOcc.stayType,
        responsibleOrganizationId: oldOcc.responsibleOrganizationId,
        notes: `从${oldOcc.roomId}号房换入`,
        bedId: dto.newBedId,
        lockerId: dto.newLockerId,
      });
      await runner.manager.save(newOcc);

      // 更新新床位/柜子状态
      if (dto.newBedId) {
        await runner.manager.update(Bed, dto.newBedId, { status: 'occupied' });
      }
      if (dto.newLockerId) {
        await runner.manager.update(Locker, dto.newLockerId, { status: 'occupied' });
      }

      if (count + 1 >= (newRoom.capacity ?? 0)) {
        await runner.manager.update(Room, newRoom.id, { status: 'full' });
      }

      await runner.commitTransaction();
      return { oldOccupancy: oldOcc, newOccupancy: newOcc };
    } catch (err) {
      await runner.rollbackTransaction();
      throw err;
    } finally {
      await runner.release();
    }
  }

  // 获取某宿舍在指定时间段内的入住记录（用于计费）
  async getRoomOccupanciesInPeriod(roomId: number, periodStart: string, periodEnd: string) {
    return this.repo
      .createQueryBuilder('o')
      .leftJoinAndSelect('o.student', 'student')
      .where('o.roomId = :roomId', { roomId })
      .andWhere('o.billingStartDate <= :periodEnd', { periodEnd })
      .andWhere('(o.billingEndDate IS NULL OR o.billingEndDate >= :periodStart)', { periodStart })
      .getMany();
  }

  async remove(id: number) {
    const occ = await this.repo.findOne({ where: { id } });
    if (!occ) throw new NotFoundException('入住记录不存在');
    if (!occ.checkOutDate) throw new BadRequestException('在住记录不能归档，请先办理退宿');
    if (occ.status === 'archived') throw new BadRequestException('入住记录已归档');
    await this.repo.update(id, { status: 'archived' });
    return { message: '已归档' };
  }

  async batchRemove(ids: number[]) {
    if (!ids || ids.length === 0) throw new BadRequestException('请选择要归档的记录');
    const records = await this.repo.find({ where: { id: In(ids) }, relations: ['student'] });
    const skipped: string[] = [];
    const deletableIds: number[] = [];
    for (const occ of records) {
      if (!occ.checkOutDate) {
        skipped.push(occ.student?.name || `记录${occ.id}`);
      } else {
        deletableIds.push(occ.id);
      }
    }
    let archived = 0;
    if (deletableIds.length > 0) {
      const result = await this.repo
        .createQueryBuilder()
        .update()
        .set({ status: 'archived' })
        .where('id IN (:...ids)', { ids: deletableIds })
        .execute();
      archived = result.affected || 0;
    }
    const message =
      skipped.length > 0
        ? `成功归档 ${archived} 条；${skipped.length} 条在住记录被跳过（${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}），请先办理退宿`
        : `批量归档成功，共 ${archived} 条`;
    return { message, archived, skipped: skipped.length };
  }

  async batchCheckOut(dto: {
    ids: number[];
    checkOutDate: string;
    billingEndDate?: string;
    checkOutReason?: string;
  }) {
    if (!dto.ids || dto.ids.length === 0) {
      throw new BadRequestException('请选择要退宿的记录');
    }
    const runner = this.dataSource.createQueryRunner();
    await runner.connect();
    await runner.startTransaction();
    let success = 0;
    const errors: string[] = [];
    try {
      for (const id of dto.ids) {
        const occ = await runner.manager.findOne(Occupancy, {
          where: { id },
          relations: ['student'],
        });
        if (!occ) {
          errors.push(`记录${id}不存在`);
          continue;
        }
        if (occ.checkOutDate) {
          errors.push(`${occ.student?.name || id}已退宿`);
          continue;
        }
        try {
          this.assertDateOrder(occ.checkInDate, dto.checkOutDate, '退宿日期不能早于入住日期');
          this.assertDateOrder(
            occ.billingStartDate || occ.checkInDate,
            dto.billingEndDate || dto.checkOutDate,
            '计费截止日不能早于计费起始日',
          );
        } catch (error) {
          errors.push(`${occ.student?.name || id}: ${(error as BadRequestException).message}`);
          continue;
        }
        occ.checkOutDate = dto.checkOutDate;
        occ.billingEndDate = dto.billingEndDate || dto.checkOutDate;
        occ.checkOutReason = dto.checkOutReason || '';
        await runner.manager.save(occ);
        // 更新房间状态
        await runner.manager.update(Room, occ.roomId, { status: 'available' });
        // 释放床位/柜子
        if (occ.bedId) await runner.manager.update(Bed, occ.bedId, { status: 'available' });
        if (occ.lockerId)
          await runner.manager.update(Locker, occ.lockerId, { status: 'available' });
        success++;
      }
      await runner.commitTransaction();
    } catch (err) {
      await runner.rollbackTransaction();
      throw err;
    } finally {
      await runner.release();
    }
    return {
      success,
      failed: errors.length,
      message: `已成功退宿 ${success} 人${errors.length > 0 ? `，${errors.length} 条失败` : ''}`,
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  /**
   * 一键导入入住名单
   * 每行数据：姓名、电话、学号、房间号、楼栋、入住日期
   * 自动创建不存在的学生和宿舍，并登记入住
   */
  async batchImportCheckIn(
    rows: {
      name: string;
      phone?: string;
      studentNo?: string;
      idNumber?: string;
      gender?: string;
      ethnicity?: string;
      emergencyContact?: string;
      emergencyPhone?: string;
      supervisor?: string;
      roomNumber: string;
      building?: string;
      checkInDate: string;
      billingStartDate?: string;
      checkOutDate?: string;
      bedNumber?: string;
      lockerNumber?: string;
      stayType?: string;
      notes?: string;
    }[],
    options?: { autoDeposit?: boolean; depositAmount?: number },
  ) {
    let imported = 0;
    let skipped = 0;
    let depositsCreated = 0;
    const errors: string[] = [];
    const importDepositAmount = options?.autoDeposit
      ? this.normalizePositiveMoney(options.depositAmount ?? 500, '押金金额')
      : undefined;

    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const rowNum = i + 2; // Excel第2行开始（第1行是表头）

      if (!row.name?.trim() || !row.roomNumber?.trim()) {
        skipped++;
        continue;
      }

      try {
        const result = await this.dataSource.transaction(async (manager) => {
          const occupancyRepo = manager.getRepository(Occupancy);
          const roomRepo = manager.getRepository(Room);
          const studentRepo = manager.getRepository(Student);
          const depositRepo = manager.getRepository(Deposit);
          const bedRepo = manager.getRepository(Bed);
          const lockerRepo = manager.getRepository(Locker);
          const organizationRepo = manager.getRepository(Organization);
          let rowDepositsCreated = 0;

          // 1. 通过手机号关联学生；未找到时创建学生并归入本机构
          const phone = row.phone?.trim();
          if (!phone) throw new BadRequestException('手机号不能为空，无法关联学生');

          let student = await studentRepo.findOne({ where: { phone } });
          if (!student) {
            const hostOrganization = await organizationRepo.findOne({
              where: { isHost: true, status: 'active' },
            });
            if (!hostOrganization) throw new BadRequestException('尚未配置本机构');

            student = await studentRepo.save(
              studentRepo.create({
                name: row.name.trim(),
                phone,
                studentNo: row.studentNo?.trim() || undefined,
                idNumber: row.idNumber?.trim() || undefined,
                gender: row.gender?.trim() || undefined,
                ethnicity: row.ethnicity?.trim() || undefined,
                emergencyContact: row.emergencyContact?.trim() || undefined,
                emergencyPhone: row.emergencyPhone?.trim() || undefined,
                organizationId: hostOrganization.id,
                supervisor: row.supervisor?.trim() || undefined,
              }),
            );
          } else {
            // 更新已有学生的缺失信息
            const updates: any = {};
            if (!student.studentNo && row.studentNo?.trim()) updates.studentNo = row.studentNo.trim();
            if (!student.idNumber && row.idNumber?.trim()) updates.idNumber = row.idNumber.trim();
            if (!student.gender && row.gender?.trim()) updates.gender = row.gender.trim();
            if (!student.ethnicity && row.ethnicity?.trim()) updates.ethnicity = row.ethnicity.trim();
            if (!student.emergencyContact && row.emergencyContact?.trim())
              updates.emergencyContact = row.emergencyContact.trim();
            if (!student.emergencyPhone && row.emergencyPhone?.trim())
              updates.emergencyPhone = row.emergencyPhone.trim();
            if (!student.supervisor && row.supervisor?.trim())
              updates.supervisor = row.supervisor.trim();
            if (Object.keys(updates).length > 0) {
              await studentRepo.update(student.id, updates);
              Object.assign(student, updates);
            }
          }

          // 2. 查找或创建宿舍（使用智能解析）
          let room = await roomRepo.findOne({ where: { roomNumber: row.roomNumber.trim() } });
          if (!room) {
            const parsed = RoomsService.parseRoomNumber(row.roomNumber.trim());
            room = await roomRepo.save(
              roomRepo.create({
                roomNumber: row.roomNumber.trim(),
                building: row.building?.trim() || parsed.building || undefined,
                floor: parsed.floor || undefined,
                capacity: parsed.capacity || 4,
                roomType: parsed.roomType || undefined,
              }),
            );
          }

          const checkInDate = row.checkInDate?.trim() || new Date().toISOString().split('T')[0];
          const checkOutDate = row.checkOutDate?.trim();
          const billingStartDate = row.billingStartDate?.trim() || checkInDate;
          const isHistoricalRecord = Boolean(checkOutDate);
          this.assertDateOnly(checkInDate, '入住日期');
          this.assertDateOnly(billingStartDate, '计费起始日');
          this.assertDateOrder(checkInDate, billingStartDate, '计费起始日不能早于入住日期');
          if (checkOutDate) {
            this.assertDateOnly(checkOutDate, '退宿日期');
            this.assertDateOrder(checkInDate, checkOutDate, '退宿日期不能早于入住日期');
            this.assertDateOrder(billingStartDate, checkOutDate, '退宿日期不能早于计费起始日');
          }

          // 3. 检查是否已有活跃入住（历史记录不影响当前入住）
          const existing = await occupancyRepo.findOne({
            where: { studentId: student.id, checkOutDate: IsNull() },
            relations: ['room'],
          });
          if (existing && !isHistoricalRecord) {
            throw new ImportRowSkipped(
              `第${rowNum}行: ${row.name} 已在住（${existing.room?.roomNumber || '房间' + existing.roomId}），跳过`,
            );
          }

          // 4. 检查宿舍容量
          const count = await occupancyRepo.count({ where: { roomId: room.id, checkOutDate: IsNull() } });
          if (!isHistoricalRecord && count >= (room.capacity ?? 0)) {
            throw new ImportRowSkipped(
              `第${rowNum}行: 宿舍 ${row.roomNumber} 已满（${count}/${room.capacity ?? '?'}），跳过 ${row.name}`,
            );
          }

          // 5. 匹配或创建床位、柜子，并校验是否可用
          let bed: Bed | null = null;
          if (row.bedNumber?.trim()) {
            const bedNumber = row.bedNumber.trim();
            bed = await bedRepo.findOne({ where: { roomId: room.id, bedNumber } });
            if (!bed) {
              const existingBedCount = await bedRepo.count({ where: { roomId: room.id } });
              if (existingBedCount >= (room.capacity ?? 0)) {
                throw new BadRequestException(
                  `宿舍 ${room.roomNumber} 已有 ${existingBedCount} 张床位，不能超过额定人数 ${room.capacity ?? '?'}`,
                );
              }
              bed = await bedRepo.save(
                bedRepo.create({ roomId: room.id, bedNumber, status: 'available' }),
              );
            }
            if (!isHistoricalRecord && bed.status !== 'available') {
              throw new BadRequestException(`床位 ${bedNumber} 已被占用或维修中`);
            }
          }

          let locker: Locker | null = null;
          if (row.lockerNumber?.trim()) {
            const lockerNumber = row.lockerNumber.trim();
            locker = await lockerRepo.findOne({ where: { roomId: room.id, lockerNumber } });
            if (!locker) {
              locker = await lockerRepo.save(
                lockerRepo.create({ roomId: room.id, lockerNumber, status: 'available' }),
              );
            }
            if (!isHistoricalRecord && locker.status !== 'available') {
              throw new BadRequestException(`柜子 ${lockerNumber} 已被占用或维修中`);
            }
          }

          // 6. 创建入住记录
          const occData: any = {
            studentId: student.id,
            roomId: room.id,
            checkInDate,
            billingStartDate,
            stayType: row.stayType || undefined,
            responsibleOrganizationId: student.organizationId,
            notes: row.notes || undefined,
            bedId: bed?.id,
            lockerId: locker?.id,
          };
          // 如果有退宿日期，直接记录
          if (checkOutDate) {
            occData.checkOutDate = checkOutDate;
            occData.billingEndDate = checkOutDate;
          }
          await occupancyRepo.save(occupancyRepo.create(occData));

          // 7. 更新床位、柜子和宿舍状态
          if (!isHistoricalRecord) {
            if (bed) await bedRepo.update(bed.id, { status: 'occupied' });
            if (locker) await lockerRepo.update(locker.id, { status: 'occupied' });
            if (count + 1 >= (room.capacity ?? 0)) {
              await roomRepo.update(room.id, { status: 'full' });
            }
          }

          // 9. 自动收取押金（仅对新入住且非历史记录的学生）
          if (options?.autoDeposit && !isHistoricalRecord) {
            const existingDeposit = await depositRepo.findOne({
              where: { studentId: student.id },
            });
            const depositAmount = importDepositAmount!;
            const hasPaidDeposit =
              existingDeposit?.status === 'paid' && Number(existingDeposit.amount || 0) > 0;
            if (hasPaidDeposit) {
              // 导入重试或重复导入时，已有已缴押金不重复收取。
            } else if (existingDeposit) {
              existingDeposit.amount = depositAmount;
              existingDeposit.status = 'paid';
              existingDeposit.paidDate = checkInDate;
              existingDeposit.refundDate = null as unknown as string;
              existingDeposit.refundAmount = null as unknown as number;
              existingDeposit.refundedBy = null;
              existingDeposit.refundedAt = null;
              existingDeposit.notes = '入住导入自动收取';
              await depositRepo.save(existingDeposit);
              rowDepositsCreated++;
            } else {
              await depositRepo.save(
                depositRepo.create({
                  studentId: student.id,
                  amount: depositAmount,
                  paidDate: checkInDate,
                  status: 'paid',
                  notes: '入住导入自动收取',
                }),
              );
              rowDepositsCreated++;
            }
          }

          return { depositsCreated: rowDepositsCreated };
        });

        imported++;
        depositsCreated += result.depositsCreated;
      } catch (e: any) {
        errors.push(
          e instanceof ImportRowSkipped
            ? e.message
            : `第${rowNum}行: ${row.name} 导入失败 - ${e.message}`,
        );
        skipped++;
      }
    }

    const depositMsg = depositsCreated > 0 ? `，自动收取 ${depositsCreated} 笔押金` : '';
    return {
      message: `成功导入 ${imported} 条入住记录，跳过 ${skipped} 条${depositMsg}`,
      imported,
      skipped,
      depositsCreated,
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  private normalizePositiveMoney(value: number, label: string): number {
    const amount = Number(value);
    if (!Number.isFinite(amount) || Math.abs(amount * 100 - Math.round(amount * 100)) > 1e-8) {
      throw new BadRequestException(`${label}最多保留两位小数`);
    }
    if (amount <= 0) throw new BadRequestException(`${label}必须大于0`);
    return Number(amount.toFixed(2));
  }

  private assertDateOnly(value: string, label: string): void {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
      throw new BadRequestException(`${label}必须为有效的 YYYY-MM-DD 日期`);
    }
    const [year, month, day] = value.split('-').map(Number);
    const date = new Date(Date.UTC(year, month - 1, day));
    if (
      date.getUTCFullYear() !== year ||
      date.getUTCMonth() + 1 !== month ||
      date.getUTCDate() !== day
    ) {
      throw new BadRequestException(`${label}必须为有效的 YYYY-MM-DD 日期`);
    }
  }

  private assertDateOrder(start: string, end: string | undefined, message: string): void {
    this.assertDateOnly(start, '起始日期');
    if (!end) return;
    this.assertDateOnly(end, '结束日期');
    if (end < start) throw new BadRequestException(message);
  }
}
