import { Injectable, NotFoundException, BadRequestException, Optional } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository, IsNull, Not, In } from 'typeorm';

import { Room } from '../entities/room.entity';
import { Occupancy } from '../entities/occupancy.entity';
import { RoomExpense } from '../entities/room-expense.entity';
import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { CreateRoomDto, UpdateRoomDto } from './dto/room.dto';
import { CreateBedDto, UpdateBedDto, BatchCreateBedDto } from './dto/bed.dto';
import { CreateLockerDto, UpdateLockerDto, BatchCreateLockerDto } from './dto/locker.dto';
import { RoomInspectionsService } from './room-inspections.service';
import { RoomQueryService } from './room-query.service';
import { RoomBedLockerService } from './room-bed-locker.service';
import { parseRoomNumber } from './room-number';

@Injectable()
export class RoomsService {
  constructor(
    @InjectRepository(Room) private repo: Repository<Room>,
    @InjectRepository(Occupancy) private occRepo: Repository<Occupancy>,
    @InjectRepository(RoomExpense) private roomExpRepo: Repository<RoomExpense>,
    @InjectRepository(Bed) private bedRepo: Repository<Bed>,
    @InjectRepository(Locker) private lockerRepo: Repository<Locker>,
    private dataSource: DataSource,
    private readonly inspectionsService: RoomInspectionsService,
    @Optional() private queryService?: RoomQueryService,
    @Optional() private beds?: RoomBedLockerService,
  ) {}

  private get queries(): RoomQueryService {
    if (!this.queryService) {
      this.queryService = new RoomQueryService(this.repo, this.occRepo, this.bedRepo, this.inspectionsService);
    }
    return this.queryService;
  }

  private get bedOps(): RoomBedLockerService {
    if (!this.beds) {
      this.beds = new RoomBedLockerService(this.repo, this.bedRepo, this.lockerRepo);
    }
    return this.beds;
  }

  /**
   * 智能解析房间号，自动推导楼栋、楼层、宿舍类型
   * "4-102"   → building:"4号楼", floor:1, roomType:"四人间"
   * "1-2-101" → building:"1-2栋", floor:1, roomType:"家庭房"
   * "3-301"   → building:"3号楼", floor:3, roomType:"四人间"
   * "8-102"   → building:"8号楼", floor:1, roomType:"爆改房"
   */
  static parseRoomNumber(roomNumber: string) {
    return parseRoomNumber(roomNumber);
  }

  async findAll(query?: { building?: string; includeArchived?: boolean }) {
    const where: any = {};
    if (query?.building) where.building = query.building;
    if (!query?.includeArchived) where.status = Not('archived');
    return this.repo.find({ where, order: { roomNumber: 'ASC' } });
  }

  async findOne(id: number) {
    const room = await this.repo.findOne({ where: { id } });
    if (!room) throw new NotFoundException('宿舍不存在');
    return room;
  }

  async findOneWithOccupants(id: number) {
    const room = await this.findOne(id);
    const occupants = await this.occRepo.find({
      where: { roomId: id, checkOutDate: IsNull() },
      relations: ['student'],
      order: { checkInDate: 'ASC' },
    });
    return { ...room, currentOccupants: occupants };
  }

  async getRoomOverview(query?: { includeArchived?: boolean }) {
    const where: any = {};
    if (!query?.includeArchived) where.status = Not('archived');
    const rooms = await this.repo.find({ where, order: { building: 'ASC', roomNumber: 'ASC' } });
    const result: any[] = [];
    for (const room of rooms) {
      const count = await this.occRepo.count({
        where: { roomId: room.id, checkOutDate: IsNull() },
      });
      result.push({ ...room, currentCount: count });
    }
    return result;
  }

  async create(dto: CreateRoomDto) {
    const parsed = RoomsService.parseRoomNumber(dto.roomNumber);
    const entity = this.repo.create({
      ...dto,
      building: dto.building ?? parsed.building,
      floor: dto.floor ?? parsed.floor,
      roomType: dto.roomType ?? parsed.roomType,
      capacity: dto.capacity ?? parsed.capacity,
    });
    const room = await this.repo.save(entity);
    await this.createDefaultBeds(room.id, room.capacity);
    return room;
  }

  async update(id: number, dto: UpdateRoomDto) {
    return this.dataSource.transaction(async (manager) => {
      const roomRepo = manager.getRepository(Room);
      const bedRepo = manager.getRepository(Bed);
      const occupancyRepo = manager.getRepository(Occupancy);
      const room = await roomRepo.findOne({ where: { id } });
      if (!room) throw new NotFoundException('宿舍不存在');

      if (dto.capacity !== undefined) {
        const [beds, activeOccupantCount] = await Promise.all([
          bedRepo.find({ where: { roomId: id }, order: { bedNumber: 'ASC' } }),
          occupancyRepo.count({ where: { roomId: id, checkOutDate: IsNull() } }),
        ]);

        if (dto.capacity < activeOccupantCount) {
          throw new BadRequestException(
            `额定人数不能少于当前入住人数，当前有 ${activeOccupantCount} 人入住`,
          );
        }
        if (dto.capacity < beds.length) {
          throw new BadRequestException(
            `额定人数不能少于现有床位数，当前有 ${beds.length} 张床位，请先删除多余的空闲床位`,
          );
        }

        if (dto.capacity > beds.length) {
          const countToCreate = dto.capacity - beds.length;
          const start = this.getNextBedNumber(beds);
          const newBeds = Array.from({ length: countToCreate }, (_, index) =>
            bedRepo.create({ roomId: id, bedNumber: `${start + index}号床` }),
          );
          await bedRepo.save(newBeds);
        }

        if (
          dto.status === undefined &&
          room.status !== 'maintenance' &&
          room.status !== 'archived'
        ) {
          dto = {
            ...dto,
            status: activeOccupantCount >= dto.capacity ? 'full' : 'available',
          };
        }
      }

      await roomRepo.update(id, dto);
      return roomRepo.findOne({ where: { id } });
    });
  }

  async remove(id: number) {
    const room = await this.findOne(id);
    // 检查是否有在住人员
    const activeCount = await this.occRepo.count({ where: { roomId: id, checkOutDate: IsNull() } });
    if (activeCount > 0) throw new BadRequestException('该宿舍有在住人员，无法归档');
    if (room.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 rooms = await this.repo.find({ where: { id: In(ids) } });
    const skipped: string[] = [];
    const targetIds: number[] = [];
    for (const r of rooms) {
      if (r.status === 'archived') {
        skipped.push(`${r.roomNumber}（已归档）`);
        continue;
      }
      const activeCount = await this.occRepo.count({
        where: { roomId: r.id, checkOutDate: IsNull() },
      });
      if (activeCount > 0) {
        skipped.push(`${r.roomNumber}（有在住人员）`);
        continue;
      }
      targetIds.push(r.id);
    }
    let affected = 0;
    if (targetIds.length > 0) {
      const result = await this.repo
        .createQueryBuilder()
        .update()
        .set({ status: 'archived' })
        .where('id IN (:...ids)', { ids: targetIds })
        .execute();
      affected = result.affected || 0;
    }
    const message =
      skipped.length > 0
        ? `成功归档 ${affected} 间；${skipped.length} 间被跳过（${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}）`
        : `已批量归档 ${affected} 间宿舍（数据已保留，可随时恢复）`;
    return { message, archived: affected, skipped: skipped.length };
  }

  async restore(id: number) {
    const room = await this.findOne(id);
    if (room.status !== 'archived') throw new BadRequestException('该宿舍未被归档');
    await this.repo.update(id, { status: 'available' });
    return { message: '已恢复' };
  }

  async purge(id: number) {
    const room = await this.findOne(id);
    if (room.status !== 'archived')
      throw new BadRequestException('仅已归档宿舍可以永久删除，请先归档');
    const [occupancyCount, expenseCount] = await Promise.all([
      this.occRepo.count({ where: { roomId: id } }),
      this.roomExpRepo.count({ where: { roomId: id } }),
    ]);
    if (occupancyCount > 0) throw new BadRequestException('该宿舍存在入住记录，无法永久删除');
    if (expenseCount > 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 rooms = await this.repo.find({ where: { id: In(uniqueIds) } });
    if (rooms.length !== uniqueIds.length) throw new NotFoundException('部分宿舍不存在');

    const deleted: number[] = [];
    const skipped: string[] = [];
    for (const room of rooms) {
      if (room.status !== 'archived') {
        skipped.push(`${room.roomNumber}（未归档）`);
        continue;
      }
      const [occupancyCount, expenseCount] = await Promise.all([
        this.occRepo.count({ where: { roomId: room.id } }),
        this.roomExpRepo.count({ where: { roomId: room.id } }),
      ]);
      if (occupancyCount > 0 || expenseCount > 0) {
        skipped.push(`${room.roomNumber}（存在关联数据）`);
        continue;
      }
      await this.repo.delete(room.id);
      deleted.push(room.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 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 rooms = await this.repo.find({ where: { id: In(uniqueIds) } });
    if (rooms.length !== uniqueIds.length) throw new NotFoundException('部分宿舍不存在');

    const targetIds = rooms.filter((room) => room.status === 'archived').map((room) => room.id);
    const skipped = rooms.length - targetIds.length;
    let restored = 0;
    if (targetIds.length > 0) {
      const result = await this.repo
        .createQueryBuilder()
        .update()
        .set({ status: 'available' })
        .where('id IN (:...ids)', { ids: targetIds })
        .execute();
      restored = result.affected || 0;
    }
    return { message: `已批量恢复 ${restored} 间宿舍`, restored, skipped };
  }
  async agentSearchRooms(query: { building?: string; keyword?: string; status?: string; limit?: number }) {
    return this.queries.agentSearchRooms(query);
  }

  async agentGetRoomOccupancySummary(query: { date?: string; building?: string; limit?: number }) {
    return this.queries.agentGetRoomOccupancySummary(query);
  }

  async getRoomVisual(asOf?: string) {
    return this.queries.getRoomVisual(asOf);
  }

  async batchImport(
    rows: {
      roomNumber: string;
      building?: string;
      floor?: number;
      capacity?: number;
      roomType?: string;
      rentalCategory?: string;
      monthlyRate?: number;
    }[],
  ) {
    return this.queries.batchImport(rows);
  }

  private createDefaultBeds(roomId: number, capacity: number): Promise<void> {
    return this.queries.createDefaultBeds(roomId, capacity);
  }

  private getNextBedNumber(beds: Pick<Bed, 'bedNumber'>[]): number {
    return this.bedOps.getNextBedNumber(beds);
  }

  async getRoomBeds(roomId: number): Promise<Bed[]> {
    return this.bedOps.getRoomBeds(roomId);
  }

  async getRoomAvailableBeds(roomId: number): Promise<Bed[]> {
    return this.bedOps.getRoomAvailableBeds(roomId);
  }

  async createBed(roomId: number, dto: CreateBedDto): Promise<Bed> {
    return this.bedOps.createBed(roomId, dto);
  }

  async updateBed(roomId: number, id: number, dto: UpdateBedDto): Promise<Bed> {
    return this.bedOps.updateBed(roomId, id, dto);
  }

  async deleteBed(roomId: number, id: number): Promise<void> {
    return this.bedOps.deleteBed(roomId, id);
  }

  async batchCreateBeds(roomId: number, dto: BatchCreateBedDto): Promise<Bed[]> {
    return this.bedOps.batchCreateBeds(roomId, dto);
  }

  async getRoomLockers(roomId: number): Promise<Locker[]> {
    return this.bedOps.getRoomLockers(roomId);
  }

  async getRoomAvailableLockers(roomId: number): Promise<Locker[]> {
    return this.bedOps.getRoomAvailableLockers(roomId);
  }

  async createLocker(roomId: number, dto: CreateLockerDto): Promise<Locker> {
    return this.bedOps.createLocker(roomId, dto);
  }

  async updateLocker(roomId: number, id: number, dto: UpdateLockerDto): Promise<Locker> {
    return this.bedOps.updateLocker(roomId, id, dto);
  }

  async deleteLocker(roomId: number, id: number): Promise<void> {
    return this.bedOps.deleteLocker(roomId, id);
  }

  async batchCreateLockers(roomId: number, dto: BatchCreateLockerDto): Promise<Locker[]> {
    return this.bedOps.batchCreateLockers(roomId, dto);
  }

}