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


@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,
    @InjectRepository(RoomInspectionDetail)
    private inspectionDetailRepo: Repository<RoomInspectionDetail>,
    @Optional() private operations?: OccupancyOperationsService,
  ) {}

  private get ops(): OccupancyOperationsService {
    if (!this.operations) {
      this.operations = new OccupancyOperationsService(
        this.repo,
        this.roomRepo,
        this.studentRepo,
        this.depositRepo,
        this.bedRepo,
        this.lockerRepo,
        this.organizationRepo,
        this.dataSource,
        this.inspectionDetailRepo,
      );
    }
    return this.operations;
  }

  async findAll(query?: {
    roomId?: number;
    studentId?: number;
    active?: boolean;
    status?: 'active' | 'archived';
  }) {
    const status = query?.status ?? 'active';
    if (status !== 'active' && status !== 'archived')
      throw new BadRequestException('入住记录状态无效');
    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 })
      .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;
    });
  }

  private withPessimisticWriteLock<T extends ObjectLiteral>(
    qb: SelectQueryBuilder<T>,
  ): SelectQueryBuilder<T> {
    return qb.setLock('pessimistic_write');
  }

  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.ops.checkOut(occupancyId, dto);
  }

  async transferRoom(occupancyId: number, dto: TransferRoomDto) {
    return this.ops.transferRoom(occupancyId, dto);
  }

  async getRoomOccupanciesInPeriod(roomId: number, periodStart: string, periodEnd: string) {
    return this.ops.getRoomOccupanciesInPeriod(roomId, periodStart, periodEnd);
  }

  async remove(id: number) {
    return this.ops.remove(id);
  }

  async batchRemove(ids: number[]) {
    return this.ops.batchRemove(ids);
  }

  async batchRestore(ids: number[]) {
    return this.ops.batchRestore(ids);
  }

  async purge(id: number) {
    return this.ops.purge(id);
  }

  async batchPurge(ids: number[]) {
    return this.ops.batchPurge(ids);
  }

  async batchCheckOut(dto: {
    ids: number[];
    checkOutDate: string;
    billingEndDate?: string;
    checkOutReason?: string;
  }) {
    return this.ops.batchCheckOut(dto);
  }

  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.ops.batchImportCheckIn(rows, options);
  }
}
