import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
  DataSource,
  Repository,
  Like,
  IsNull,
  Not,
  In,
  LessThanOrEqual,
} 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 { occupancyWhereOnDate } from './room-occupancy-date';

@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,
  ) {}

  /**
   * 智能解析房间号，自动推导楼栋、楼层、宿舍类型
   * "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): {
    building?: string;
    floor?: number;
    roomType?: string;
    capacity?: number;
  } {
    const cleaned = roomNumber.replace(/[（(].*?[）)]/g, '').trim();
    // 家庭房: X-Y-ZZZ 格式
    const familyMatch = cleaned.match(/^(\d+)-(\d+)-(\d+)$/);
    if (familyMatch) {
      const bldg = `${familyMatch[1]}-${familyMatch[2]}栋`;
      const roomPart = familyMatch[3];
      const rawFloor = parseInt(roomPart.charAt(0), 10);
      const floor = Number.isNaN(rawFloor) ? undefined : rawFloor;
      return { building: bldg, floor, roomType: '家庭房', capacity: 4 };
    }
    // 标准: X-YZZ 格式
    const stdMatch = cleaned.match(/^(\d+)-(\d+)$/);
    if (stdMatch) {
      const bldgNum = stdMatch[1];
      const roomPart = stdMatch[2];
      const rawFloor = parseInt(roomPart.charAt(0), 10);
      const floor = Number.isNaN(rawFloor) ? undefined : rawFloor;
      const building = `${bldgNum}号楼`;
      let roomType = '四人间';
      let capacity = 4;
      if (bldgNum === '2') {
        roomType = '单人间';
        capacity = 1;
      } else if (bldgNum === '8') {
        roomType = '爆改房';
        capacity = 2;
      }
      return { building, floor, roomType, capacity };
    }
    return { capacity: 4, roomType: '四人间' };
  }

  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 getRoomVisual(asOf?: string) {
    // asOf 为空 = 实时（今天）。带 asOf = 还原该日期结束时的历史入住快照。
    const isHistorical = !!asOf;
    const targetDate = asOf || this.getChinaDate(new Date());

    // 实时视图排除已归档房间；历史视图不排除——当时有人住的房间即使现在已归档也应显示。
    const rooms = await this.repo.find({
      where: isHistorical ? {} : { status: Not('archived') },
      order: { building: 'ASC', roomNumber: 'ASC' },
    });

    const occupancies = await this.occRepo.find({
      where: occupancyWhereOnDate(targetDate),
      relations: ['student', 'student.organization', 'responsibleOrganization', 'bed'],
      order: { checkInDate: 'ASC' },
    });

    // 按roomId分组入住记录
    const occMap = new Map<number, any[]>();
    // days（已住天数）相对目标日期计算，而非固定今天，历史视图才准确。
    const refTime = new Date(targetDate).getTime();
    for (const occ of occupancies) {
      if (!occMap.has(occ.roomId)) occMap.set(occ.roomId, []);
      const checkIn = new Date(occ.checkInDate);
      const days = Math.max(1, Math.ceil((refTime - checkIn.getTime()) / (1000 * 60 * 60 * 24)));
      occMap.get(occ.roomId)!.push({
        studentId: occ.studentId,
        occupancyId: occ.id,
        studentName: occ.student?.name || '未知',
        bedId: occ.bedId ?? null,
        bedNumber: occ.bed?.bedNumber || null,
        checkInDate: occ.checkInDate,
        billingStartDate: occ.billingStartDate,
        days,
        organization: occ.student?.organization?.name || null,
        supervisor: occ.student?.supervisor || null,
        organizationId: occ.responsibleOrganizationId || null,
        organizationName: occ.responsibleOrganization?.name || null,
        organizationColor: occ.responsibleOrganization?.color || null,
      });
    }

    // 获取各楼栋列表
    const buildings = [...new Set(rooms.map((r) => r.building).filter(Boolean))];

    // 历史视图纳入了已归档房间，但只保留当时确实有人住的归档房间，避免空归档房间刷屏。
    const visibleRooms = isHistorical
      ? rooms.filter((r) => r.status !== 'archived' || (occMap.get(r.id)?.length ?? 0) > 0)
      : rooms;

    // 批量获取床位统计
    const allBeds = await this.bedRepo.find({
      where: { roomId: In(visibleRooms.map((r) => r.id)) },
    });
    const bedMap = new Map<number, { total: number; occupied: number }>();
    for (const bed of allBeds) {
      if (!bedMap.has(bed.roomId)) bedMap.set(bed.roomId, { total: 0, occupied: 0 });
      const entry = bedMap.get(bed.roomId)!;
      entry.total++;
      if (bed.status === 'occupied') entry.occupied++;
    }

    const inspectionMap = await this.inspectionsService.getByRoomsAndDate(
      visibleRooms.map((room) => room.id),
      targetDate,
    );

    return {
      buildings,
      rooms: visibleRooms.map((room) => {
        const occ = occMap.get(room.id) || [];
        const inspection = inspectionMap.get(room.id);
        const inspectionByOccupancyId = new Map(
          (inspection?.details || []).map((detail) => [detail.occupancyId, detail.status]),
        );
        const orgs = [...new Set(occ.map((o: any) => o.organization).filter(Boolean))];
        let orgLabel: string | null = null;
        if (orgs.length > 0 && occ.length > 0) {
          const allSameOrg = occ.every((o: any) => o.organization && o.organization === orgs[0]);
          orgLabel = allSameOrg ? `均为${orgs[0]}人员` : `存在${orgs.join('、')}人员`;
        }
        const organizationColors = [
          ...new Set(occ.map((o: any) => o.organizationColor).filter(Boolean)),
        ];
        const organizationColor: string | null =
          organizationColors.length === 1 ? organizationColors[0] : null;
        const organizationIds = [...new Set(occ.map((o: any) => o.organizationId).filter(Boolean))];
        return {
          id: room.id,
          roomNumber: room.roomNumber,
          building: room.building,
          floor: room.floor,
          capacity: room.capacity,
          status: room.status,
          currentCount: occ.length,
          totalBeds: bedMap.get(room.id)?.total ?? 0,
          occupiedBeds: bedMap.get(room.id)?.occupied ?? 0,
          occupants: occ.map((occupant) => ({
            ...occupant,
            inspectionStatus: inspectionByOccupancyId.get(occupant.occupancyId) || null,
          })),
          inspection: inspection
            ? {
                submitted: true,
                inspectorId: inspection.inspectorId,
                inspectorName: inspection.inspectorName,
                source: inspection.source,
                submittedAt: inspection.submittedAt,
              }
            : { submitted: false },
          orgLabel,
          organizationColor,
          organizationIds,
        };
      }),
      // 当前视图内出现过的负责机构，供筛选下拉使用
      organizations: [
        ...new Map(
          occupancies
            .filter((o) => o.responsibleOrganizationId && o.responsibleOrganization)
            .map((o) => [
              o.responsibleOrganizationId,
              {
                id: o.responsibleOrganizationId,
                name: o.responsibleOrganization.name,
                color: o.responsibleOrganization.color || null,
              },
            ]),
        ).values(),
      ].sort((a, b) => a.name.localeCompare(b.name)),
    };
  }

  private getChinaDate(now: Date): string {
    return new Intl.DateTimeFormat('en-CA', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(now);
  }

  async batchImport(
    rows: {
      roomNumber: string;
      building?: string;
      floor?: number;
      capacity?: number;
      roomType?: string;
      rentalCategory?: string;
      monthlyRate?: number;
    }[],
  ) {
    let imported = 0;
    let skipped = 0;
    for (const row of rows) {
      if (!row.roomNumber || !row.roomNumber.trim()) {
        skipped++;
        continue;
      }
      const exists = await this.repo.findOne({ where: { roomNumber: row.roomNumber.trim() } });
      if (exists) {
        skipped++;
        continue;
      }
      // 智能解析房间号
      const parsed = RoomsService.parseRoomNumber(row.roomNumber.trim());
      const room = await this.repo.save(
        this.repo.create({
          roomNumber: row.roomNumber.trim(),
          building: row.building?.trim() || parsed.building || undefined,
          floor: row.floor ?? parsed.floor,
          capacity: row.capacity ?? parsed.capacity ?? 4,
          roomType: row.roomType || parsed.roomType || undefined,
          rentalCategory: row.rentalCategory || undefined,
          monthlyRate: row.monthlyRate ?? undefined,
        }),
      );
      await this.createDefaultBeds(room.id, room.capacity);
      imported++;
    }
    return {
      message: `成功导入 ${imported} 间宿舍，跳过 ${skipped} 条（重复或空行）`,
      imported,
      skipped,
    };
  }

  // ── 床位管理 ──

  async getRoomBeds(roomId: number): Promise<Bed[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    return this.bedRepo.find({ where: { roomId, status: Not('archived') }, order: { bedNumber: 'ASC' } });
  }

  async getRoomAvailableBeds(roomId: number): Promise<Bed[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    return this.bedRepo.find({
      where: { roomId, status: 'available' },
      order: { bedNumber: 'ASC' },
    });
  }

  async createBed(roomId: number, dto: CreateBedDto): Promise<Bed> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    if (room.status === 'archived') throw new BadRequestException('已归档宿舍不能添加床位');
    await this.assertCanAddBeds(room, 1);
    const existing = await this.bedRepo.findOne({ where: { roomId, bedNumber: dto.bedNumber } });
    if (existing) throw new BadRequestException('该床位编号已存在');
    const bed = this.bedRepo.create({ ...dto, roomId });
    return this.bedRepo.save(bed);
  }

  async updateBed(roomId: number, id: number, dto: UpdateBedDto): Promise<Bed> {
    const bed = await this.bedRepo.findOne({ where: { id, roomId } });
    if (!bed) throw new NotFoundException('床位不存在');
    // 不允许将 occupied 的床位改为 maintenance
    if (dto.status === 'maintenance' && bed.status === 'occupied') {
      throw new BadRequestException('该床位有人入住，请先退宿');
    }
    // 编号唯一性检查
    if (dto.bedNumber && dto.bedNumber !== bed.bedNumber) {
      const dup = await this.bedRepo.findOne({ where: { roomId, bedNumber: dto.bedNumber } });
      if (dup) throw new BadRequestException('该床位编号已存在');
    }
    Object.assign(bed, dto);
    return this.bedRepo.save(bed);
  }

  async deleteBed(roomId: number, id: number): Promise<void> {
    const bed = await this.bedRepo.findOne({ where: { id, roomId } });
    if (!bed) throw new NotFoundException('床位不存在');
    if (bed.status === 'occupied') throw new BadRequestException('该床位有人入住，无法归档');
    if (bed.status === 'archived') throw new BadRequestException('该床位已归档');
    await this.bedRepo.update(id, { status: 'archived' });
  }

  async batchCreateBeds(roomId: number, dto: BatchCreateBedDto): Promise<Bed[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    if (room.status === 'archived') throw new BadRequestException('已归档宿舍不能添加床位');
    const existing = await this.bedRepo.find({ where: { roomId, status: Not('archived') }, order: { bedNumber: 'ASC' } });
    this.assertCanAddBedsFromCount(room, existing.length, dto.count);
    const numbers = existing.map((b) => {
      const match = b.bedNumber.match(/^\d+/);
      return match ? parseInt(match[0]) : 0;
    });
    const start = numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
    const beds: Bed[] = [];
    for (let i = 0; i < dto.count; i++) {
      beds.push(this.bedRepo.create({ roomId, bedNumber: `${start + i}号床` }));
    }
    return this.bedRepo.save(beds);
  }

  private async createDefaultBeds(roomId: number, capacity: number): Promise<void> {
    const count = Math.max(capacity ?? 0, 0);
    if (count === 0) return;
    const beds = Array.from({ length: count }, (_, index) =>
      this.bedRepo.create({ roomId, bedNumber: `${index + 1}号床` }),
    );
    await this.bedRepo.save(beds);
  }

  private getNextBedNumber(beds: Pick<Bed, 'bedNumber'>[]): number {
    const numbers = beds.map((bed) => {
      const match = bed.bedNumber.match(/^\d+/);
      return match ? parseInt(match[0], 10) : 0;
    });
    return numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
  }

  private async assertCanAddBeds(room: Room, count: number): Promise<void> {
    const existingCount = await this.bedRepo.count({ where: { roomId: room.id } });
    this.assertCanAddBedsFromCount(room, existingCount, count);
  }

  private assertCanAddBedsFromCount(room: Room, existingCount: number, count: number): void {
    const remaining = Math.max((room.capacity ?? 0) - existingCount, 0);
    if (count > remaining) {
      throw new BadRequestException(
        `床位不能超过额定人数，当前已有 ${existingCount} 张，额定 ${room.capacity} 张，最多还能添加 ${remaining} 张`,
      );
    }
  }

  // ── 柜子管理 ──

  async getRoomLockers(roomId: number): Promise<Locker[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    return this.lockerRepo.find({ where: { roomId, status: Not('archived') }, order: { lockerNumber: 'ASC' } });
  }

  async getRoomAvailableLockers(roomId: number): Promise<Locker[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    return this.lockerRepo.find({
      where: { roomId, status: 'available' },
      order: { lockerNumber: 'ASC' },
    });
  }

  async createLocker(roomId: number, dto: CreateLockerDto): Promise<Locker> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    if (room.status === 'archived') throw new BadRequestException('已归档宿舍不能添加柜子');
    const existing = await this.lockerRepo.findOne({
      where: { roomId, lockerNumber: dto.lockerNumber },
    });
    if (existing) throw new BadRequestException('该柜子编号已存在');
    const locker = this.lockerRepo.create({ ...dto, roomId });
    return this.lockerRepo.save(locker);
  }

  async updateLocker(roomId: number, id: number, dto: UpdateLockerDto): Promise<Locker> {
    const locker = await this.lockerRepo.findOne({ where: { id, roomId } });
    if (!locker) throw new NotFoundException('柜子不存在');
    if (dto.status === 'maintenance' && locker.status === 'occupied') {
      throw new BadRequestException('该柜子有人占用，请先释放');
    }
    if (dto.lockerNumber && dto.lockerNumber !== locker.lockerNumber) {
      const dup = await this.lockerRepo.findOne({
        where: { roomId, lockerNumber: dto.lockerNumber },
      });
      if (dup) throw new BadRequestException('该柜子编号已存在');
    }
    Object.assign(locker, dto);
    return this.lockerRepo.save(locker);
  }

  async deleteLocker(roomId: number, id: number): Promise<void> {
    const locker = await this.lockerRepo.findOne({ where: { id, roomId } });
    if (!locker) throw new NotFoundException('柜子不存在');
    if (locker.status === 'occupied') throw new BadRequestException('该柜子有人占用，无法归档');
    if (locker.status === 'archived') throw new BadRequestException('该柜子已归档');
    await this.lockerRepo.update(id, { status: 'archived' });
  }

  async batchCreateLockers(roomId: number, dto: BatchCreateLockerDto): Promise<Locker[]> {
    const room = await this.repo.findOne({ where: { id: roomId } });
    if (!room) throw new NotFoundException('宿舍不存在');
    if (room.status === 'archived') throw new BadRequestException('已归档宿舍不能添加柜子');
    const existing = await this.lockerRepo.find({
      where: { roomId, status: Not('archived') },
      order: { lockerNumber: 'ASC' },
    });
    const numbers = existing.map((b) => {
      const match = b.lockerNumber.match(/^\d+/);
      return match ? parseInt(match[0]) : 0;
    });
    const start = numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
    const lockers: Locker[] = [];
    for (let i = 0; i < dto.count; i++) {
      lockers.push(this.lockerRepo.create({ roomId, lockerNumber: `${start + i}号柜` }));
    }
    return this.lockerRepo.save(lockers);
  }
}
