import { DataSource, 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';
import { RoomsController } from './rooms.controller';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { PERMISSION_KEY } from '../auth/decorators/permission.decorator';

// ── Controller test mocks ──────────────────────────────────────────────

const mockExcelEachRow = jest.fn();

jest.mock('exceljs', () => ({
  Workbook: jest.fn().mockImplementation(() => ({
    xlsx: { load: jest.fn().mockResolvedValue(undefined) },
    worksheets: [{ eachRow: mockExcelEachRow }],
  })),
}));

jest.mock('../common/request-utils', () => ({
  extractRequestInfo: jest.fn(() => ({ ipAddress: '::1', userAgent: 'jest' })),
}));

// ── Service: parseRoomNumber boundary conditions ───────────────────────

describe('RoomsService — parseRoomNumber boundary conditions', () => {
  // 1
  it('returns default {capacity:4, roomType:"四人间"} for unrecognised formats', () => {
    expect(RoomsService.parseRoomNumber('A101')).toEqual({ capacity: 4, roomType: '四人间' });
    expect(RoomsService.parseRoomNumber('仓库')).toEqual({ capacity: 4, roomType: '四人间' });
    expect(RoomsService.parseRoomNumber('')).toEqual({ capacity: 4, roomType: '四人间' });
  });

  // 2
  it('preserves floor 0 — parseInt("0") should not become undefined via ||', () => {
    // "4-0" in standard format: roomPart = "0", charAt(0) = "0",
    // parseInt("0", 10) = 0, which is falsy but NOT NaN.
    // The code must use Number.isNaN(), not ||, to keep floor: 0.
    const result = RoomsService.parseRoomNumber('4-0');
    expect(result.floor).toBe(0);
    expect(result.floor).not.toBeUndefined();
  });

  // 3 — moved below into the batchImport describe (needs instantiated service)

  // 4
  it('parses standard format "4-102" correctly', () => {
    const result = RoomsService.parseRoomNumber('4-102');
    expect(result).toEqual({
      building: '4号楼',
      floor: 1,
      roomType: '四人间',
      capacity: 4,
    });
  });

  // 5
  it('recognises building 2 as 单人间 (capacity 1)', () => {
    const result = RoomsService.parseRoomNumber('2-301');
    expect(result).toEqual({
      building: '2号楼',
      floor: 3,
      roomType: '单人间',
      capacity: 1,
    });
  });

  // 6
  it('recognises building 8 as 爆改房 (capacity 2)', () => {
    const result = RoomsService.parseRoomNumber('8-102');
    expect(result).toEqual({
      building: '8号楼',
      floor: 1,
      roomType: '爆改房',
      capacity: 2,
    });
  });

  // 7
  it('parses family format "1-2-101" correctly', () => {
    const result = RoomsService.parseRoomNumber('1-2-101');
    expect(result).toEqual({
      building: '1-2栋',
      floor: 1,
      roomType: '家庭房',
      capacity: 4,
    });
  });
});

describe('RoomsController — inspection permission boundary', () => {
  it('requires the dedicated room:inspect permission', () => {
    expect(Reflect.getMetadata(PERMISSION_KEY, RoomsController.prototype.updateInspection)).toEqual([
      'room:inspect',
    ]);
  });
});

// ── Service: batchImport boundary conditions ───────────────────────────

describe('RoomsService — batchImport boundary conditions', () => {
  const createService = () => {
    const saved: any[] = [];

    const roomRepo = {
      findOne: jest.fn().mockResolvedValue(null),
      create: jest.fn((data: any) => data),
      save: jest.fn().mockImplementation((entity: any) => {
        const record = { id: saved.length + 1, ...entity };
        saved.push(record);
        return Promise.resolve(record);
      }),
    } as unknown as Repository<Room>;

    const bedRepo = {
      create: jest.fn((data: any) => data),
      save: jest.fn().mockResolvedValue([]),
    } as unknown as Repository<Bed>;

    const occupancyRepo = {} as unknown as Repository<Occupancy>;
    const roomExpRepo = {} as unknown as Repository<RoomExpense>;
    const lockerRepo = {} as unknown as Repository<Locker>;
    const dataSource = {} as unknown as DataSource;

    const service = new RoomsService(
      roomRepo,
      occupancyRepo,
      roomExpRepo,
      bedRepo,
      lockerRepo,
      dataSource,
      { getByRoomsAndDate: jest.fn().mockResolvedValue(new Map()) } as never,
    );

    return { service, roomRepo, bedRepo };
  };

  // 3
  it('preserves explicit capacity 0 via ?? (not ||)', async () => {
    const { service, roomRepo } = createService();

    await service.batchImport([
      { roomNumber: '4-102', capacity: 0 },
    ]);

    // If || were used, 0 would be falsy and fall through to parsed.capacity (4).
    // Because ?? is used, explicit 0 stays 0.
    expect(roomRepo.save).toHaveBeenCalledWith(
      expect.objectContaining({ capacity: 0, roomNumber: '4-102' }),
    );
    expect(roomRepo.save).toHaveBeenCalledTimes(1);
  });

  it('falls back to parsed capacity when row.capacity is undefined', async () => {
    const { service, roomRepo } = createService();

    await service.batchImport([
      { roomNumber: '4-102' },           // no capacity field
      { roomNumber: '2-301' },           // building 2 → parsed capacity 1
    ]);

    const calls = (roomRepo.save as jest.Mock).mock.calls;
    expect(calls[0][0]).toMatchObject({ capacity: 4, roomNumber: '4-102' });
    expect(calls[1][0]).toMatchObject({ capacity: 1, roomNumber: '2-301' });
  });

  it('falls back to the default 4 when both row.capacity and parsed.capacity are undefined', async () => {
    const { service, roomRepo } = createService();

    // "A101" is unrecognised → parsed = { capacity: 4, roomType: '四人间' }
    // row has no capacity → row.capacity ?? parsed.capacity ?? 4 = 4
    await service.batchImport([
      { roomNumber: 'A101' },
    ]);

    expect(roomRepo.save).toHaveBeenCalledWith(
      expect.objectContaining({ capacity: 4, roomNumber: 'A101' }),
    );
  });
});

// ── Controller: boundary conditions ────────────────────────────────────

describe('RoomsController — boundary conditions', () => {
  const mockLogService = {
    log: jest.fn().mockResolvedValue(undefined),
  } as unknown as OperationLogsService;

  const mockRoomsService = {
    batchImport: jest.fn().mockResolvedValue({
      message: '成功导入 1 间宿舍，跳过 0 条（重复或空行）',
      imported: 1,
      skipped: 0,
    }),
  } as unknown as RoomsService;

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('importExcel passes capacity=4 when Excel cell is 0 (|| instead of ??)', async () => {
    mockExcelEachRow.mockImplementation((cb: (row: any, idx: number) => void) => {
      // idx 1 = header → skip
      // idx 2 = data row
      const dataRow = {
        getCell: jest.fn((col: number) => {
          const values: Record<number, any> = {
            1: '4-102',
            2: '',
            3: 1,
            4: 0,         // <-- capacity explicitly 0 in spreadsheet
            5: '',
            6: '',
            7: '',
          };
          return { value: values[col] ?? '' };
        }),
      };
      cb(dataRow, 2);
    });

    const controller = new RoomsController(
      mockRoomsService,
      mockLogService,
      { submit: jest.fn() } as never,
    );
    const file = { buffer: Buffer.from('fake') } as Express.Multer.File;
    const req = { user: { id: 1, username: 'tester' } };

    await controller.importExcel(file, req);

    expect(mockRoomsService.batchImport).toHaveBeenCalledTimes(1);
    const rows = (mockRoomsService.batchImport as jest.Mock).mock.calls[0][0];
    expect(rows).toHaveLength(1);
    // Bug: || turns 0 into 4 — capacity should be 4, not 0
    expect(rows[0].capacity).toBe(4);
  });
});
