import { Injectable, NotFoundException, BadRequestException, Optional } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, IsNull, In } 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 { RoomInspectionDetail } from '../entities/room-inspection-detail.entity';
import { CheckOutDto, TransferRoomDto } from './dto/occupancy.dto';
import { OccupancyImportService } from './occupancy-import.service';
import { withPessimisticWriteLock } from './occupancy-lock';

@Injectable()
export class OccupancyOperationsService {
  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,
    @InjectRepository(RoomInspectionDetail)
    private inspectionDetailRepo: Repository<RoomInspectionDetail>,
    @Optional() private imports?: OccupancyImportService,
  ) {}

  private get imp(): OccupancyImportService {
    if (!this.imports) this.imports = new OccupancyImportService(this.dataSource);
    return this.imports;
  }

  private normalizePositiveMoney(value: number, label: string): number {
    if (!Number.isFinite(value) || value < 0) {
      throw new BadRequestException(`${label}必须为非负数字`);
    }
    return Math.round(value * 100) / 100;
  }

  private assertDateOnly(value: string, label: string): void {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value || '')) {
      throw new BadRequestException(`${label}格式错误，应为 YYYY-MM-DD`);
    }
    const date = new Date(`${value}T00:00:00Z`);
    if (Number.isNaN(date.getTime())) throw new BadRequestException(`${label}不是有效日期`);
  }

  private assertDateOrder(start: string, end: string | undefined, message: string): void {
    if (end && start > end) throw new BadRequestException(message);
  }

  async checkOut(occupancyId: number, dto: CheckOutDto) {
    return this.dataSource.transaction(async (manager) => {
      const occ = await withPessimisticWriteLock(
        manager
          .createQueryBuilder(Occupancy, 'occupancy')
          .where('occupancy.id = :id', { id: occupancyId }),
      this.dataSource).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 withPessimisticWriteLock(
        runner.manager
          .createQueryBuilder(Occupancy, 'occupancy')
          .where('occupancy.id = :id', { id: occupancyId }),
      this.dataSource).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 withPessimisticWriteLock(
        runner.manager
          .createQueryBuilder(Room, 'room')
          .where('room.id = :roomId', { roomId: dto.newRoomId }),
      this.dataSource).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 withPessimisticWriteLock(
          runner.manager
            .createQueryBuilder(Bed, 'bed')
            .where('bed.id = :bedId AND bed.roomId = :roomId', {
              bedId: dto.newBedId,
              roomId: dto.newRoomId,
            }),
        this.dataSource).getOne();
        if (!newBed) throw new BadRequestException('目标床位不存在或不属于目标宿舍');
        if (newBed.status !== 'available') throw new BadRequestException('目标床位已被占用');
      }
      if (dto.newLockerId) {
        const newLocker = await withPessimisticWriteLock(
          runner.manager
            .createQueryBuilder(Locker, 'locker')
            .where('locker.id = :lockerId AND locker.roomId = :roomId', {
              lockerId: dto.newLockerId,
              roomId: dto.newRoomId,
            }),
        this.dataSource).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 batchRestore(ids: number[]) {
    const uniqueIds = [...new Set(ids || [])];
    if (uniqueIds.length === 0) throw new BadRequestException('请选择要恢复的记录');
    if (uniqueIds.some((id) => !Number.isInteger(id) || id <= 0)) {
      throw new BadRequestException('入住记录 ID 无效');
    }
    const records = await this.repo.find({ where: { id: In(uniqueIds) } });
    if (records.length !== uniqueIds.length) throw new NotFoundException('部分入住记录不存在');
    if (records.some((record) => record.status === 'archived' && !record.checkOutDate)) {
      throw new BadRequestException('选中记录包含未退宿的异常归档记录');
    }

    const targetIds = records
      .filter((record) => record.status === 'archived')
      .map((record) => record.id);
    const skipped = records.length - targetIds.length;
    let restored = 0;
    if (targetIds.length > 0) {
      const result = await this.repo
        .createQueryBuilder()
        .update()
        .set({ status: 'active' })
        .where('id IN (:...ids)', { ids: targetIds })
        .execute();
      restored = result.affected || 0;
    }
    return { message: `已批量恢复 ${restored} 条入住记录`, restored, skipped };
  }

  async purge(id: number) {
    const occ = await this.repo.findOne({ where: { id } });
    if (!occ) throw new NotFoundException('入住记录不存在');
    if (occ.status !== 'archived')
      throw new BadRequestException('仅已归档入住记录可以永久删除，请先归档');
    const detailCount = await this.inspectionDetailRepo.count({ where: { occupancyId: id } });
    if (detailCount > 0) throw new BadRequestException('该入住记录已被查寝记录引用，无法永久删除');
    await this.repo.delete(id);
    return { message: '已永久删除入住记录（不可恢复）' };
  }

  async batchPurge(ids: number[]) {
    const uniqueIds = [...new Set(ids || [])];
    if (uniqueIds.length === 0) throw new BadRequestException('请选择要永久删除的入住记录');
    if (uniqueIds.some((id) => !Number.isInteger(id) || id <= 0)) {
      throw new BadRequestException('入住记录 ID 无效');
    }
    const records = await this.repo.find({ where: { id: In(uniqueIds) }, relations: ['student'] });
    if (records.length !== uniqueIds.length) throw new NotFoundException('部分入住记录不存在');

    const deleted: number[] = [];
    const skipped: string[] = [];
    for (const occ of records) {
      if (occ.status !== 'archived') {
        skipped.push(`${occ.student?.name || `记录${occ.id}`}（未归档）`);
        continue;
      }
      const detailCount = await this.inspectionDetailRepo.count({ where: { occupancyId: occ.id } });
      if (detailCount > 0) {
        skipped.push(`${occ.student?.name || `记录${occ.id}`}（存在关联数据）`);
        continue;
      }
      await this.repo.delete(occ.id);
      deleted.push(occ.id);
    }
    const message =
      skipped.length > 0
        ? `已永久删除 ${deleted.length} 条；${skipped.length} 条被跳过（${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}）`
        : `已永久删除 ${deleted.length} 条入住记录（不可恢复）`;
    return { message, deleted: deleted.length, 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 },
  ) {
    return this.imp.batchImportCheckIn(rows, options);
  }
}
