import { BadRequestException } from '@nestjs/common';
import { DataSource, EntityManager, Repository } from 'typeorm';
import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity';
import { RoomExpense } from '../entities/room-expense.entity';
import { RoomsService } from './rooms.service';

describe('RoomsService — capacity consistency', () => {
  const createService = (options?: {
    room?: Partial<Room>;
    beds?: Partial<Bed>[];
    activeOccupantCount?: number;
  }) => {
    const room = { id: 1, capacity: 2, status: 'full', ...options?.room } as Room;
    const beds = (options?.beds ?? [
      { id: 1, roomId: 1, bedNumber: '1号床' },
      { id: 2, roomId: 1, bedNumber: '2号床' },
    ]) as Bed[];

    const roomRepo = {
      findOne: jest
        .fn()
        .mockResolvedValueOnce(room)
        .mockResolvedValue({ ...room, capacity: 4 }),
      update: jest.fn().mockResolvedValue(undefined),
    } as unknown as Repository<Room>;
    const bedRepo = {
      find: jest.fn().mockResolvedValue(beds),
      create: jest.fn((value) => value),
      save: jest.fn(async (value) => value),
    } as unknown as Repository<Bed>;
    const occupancyRepo = {
      count: jest.fn().mockResolvedValue(options?.activeOccupantCount ?? 2),
    } as unknown as Repository<Occupancy>;

    const manager = {
      getRepository: jest.fn((entity) => {
        if (entity === Room) return roomRepo;
        if (entity === Bed) return bedRepo;
        if (entity === Occupancy) return occupancyRepo;
        throw new Error(`Unexpected repository: ${String(entity)}`);
      }),
    } as unknown as EntityManager;
    const dataSource = {
      transaction: jest.fn(async (callback) => callback(manager)),
    } as unknown as DataSource;

    const service = new RoomsService(
      roomRepo,
      occupancyRepo,
      {} as Repository<RoomExpense>,
      bedRepo,
      {} as Repository<Locker>,
      dataSource,
      { getByRoomsAndDate: jest.fn().mockResolvedValue(new Map()) } as never,
    );

    return { service, roomRepo, bedRepo, occupancyRepo };
  };

  it('automatically creates missing beds when capacity increases', async () => {
    const { service, roomRepo, bedRepo } = createService();

    await service.update(1, { capacity: 4 });

    expect(bedRepo.create).toHaveBeenNthCalledWith(1, { roomId: 1, bedNumber: '3号床' });
    expect(bedRepo.create).toHaveBeenNthCalledWith(2, { roomId: 1, bedNumber: '4号床' });
    expect(bedRepo.save).toHaveBeenCalledWith([
      { roomId: 1, bedNumber: '3号床' },
      { roomId: 1, bedNumber: '4号床' },
    ]);
    expect(roomRepo.update).toHaveBeenCalledWith(1, { capacity: 4, status: 'available' });
  });

  it('rejects capacity lower than the active occupant count', async () => {
    const { service, roomRepo, bedRepo } = createService({
      room: { capacity: 4, status: 'available' },
      beds: [{ id: 1, roomId: 1, bedNumber: '1号床' }],
      activeOccupantCount: 3,
    });

    await expect(service.update(1, { capacity: 2 })).rejects.toThrow(
      new BadRequestException('额定人数不能少于当前入住人数，当前有 3 人入住'),
    );
    expect(roomRepo.update).not.toHaveBeenCalled();
    expect(bedRepo.save).not.toHaveBeenCalled();
  });

  it('rejects capacity lower than the existing bed count', async () => {
    const { service, roomRepo, bedRepo } = createService({
      room: { capacity: 4, status: 'available' },
      activeOccupantCount: 1,
      beds: [
        { id: 1, roomId: 1, bedNumber: '1号床' },
        { id: 2, roomId: 1, bedNumber: '2号床' },
        { id: 3, roomId: 1, bedNumber: '3号床' },
        { id: 4, roomId: 1, bedNumber: '4号床' },
      ],
    });

    await expect(service.update(1, { capacity: 3 })).rejects.toThrow(
      new BadRequestException(
        '额定人数不能少于现有床位数，当前有 4 张床位，请先删除多余的空闲床位',
      ),
    );
    expect(roomRepo.update).not.toHaveBeenCalled();
    expect(bedRepo.save).not.toHaveBeenCalled();
  });

  it('marks the room full when a valid capacity reduction reaches the occupant count', async () => {
    const { service, roomRepo, bedRepo } = createService({
      room: { capacity: 4, status: 'available' },
      activeOccupantCount: 2,
      beds: [
        { id: 1, roomId: 1, bedNumber: '1号床' },
        { id: 2, roomId: 1, bedNumber: '2号床' },
      ],
    });

    await service.update(1, { capacity: 2 });

    expect(roomRepo.update).toHaveBeenCalledWith(1, { capacity: 2, status: 'full' });
    expect(bedRepo.save).not.toHaveBeenCalled();
  });

  it('updates other room fields without changing beds', async () => {
    const { service, roomRepo, bedRepo, occupancyRepo } = createService();

    await service.update(1, { building: '2号楼' });

    expect(roomRepo.update).toHaveBeenCalledWith(1, { building: '2号楼' });
    expect(bedRepo.find).not.toHaveBeenCalled();
    expect(occupancyRepo.count).not.toHaveBeenCalled();
  });
});
