import { BadRequestException } from '@nestjs/common';
import { SchedulesService } from './schedules.service';
import { ClassroomStatus } from '../entities';

function mockQueryBuilder<T>(results: T[] = []) {
  const qb = {
    where: jest.fn().mockReturnThis(),
    andWhere: jest.fn().mockReturnThis(),
    orderBy: jest.fn().mockReturnThis(),
    addOrderBy: jest.fn().mockReturnThis(),
    getMany: jest.fn().mockResolvedValue(results),
  };
  return qb;
}

describe('SchedulesService — boundary conditions', () => {
  const validDto = {
    classId: 1,
    classroomId: 2,
    weekDay: 1,
    startTime: '09:00',
    endTime: '10:00',
    startDate: '2026-07-01',
    endDate: '2026-07-31',
    subject: '数学',
    teacherId: 1,
  };

  /** Minimal service where only scheduleRepo.create is mocked, used for early-rejection tests. */
  function makeMinimalService() {
    const scheduleRepo = { create: jest.fn() };
    return {
      service: new SchedulesService(
        scheduleRepo as never,
        {} as never,
        {} as never,
        {} as never,
        {} as never,
        {} as never,
      ),
      scheduleRepo,
    };
  }

  /** Full service where all downstream mocks are wired so create can succeed end-to-end. */
  function makeFullService() {
    const scheduleRepo = {
      createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder([])),
      create: jest.fn(),
      save: jest.fn(),
      findOne: jest.fn(),
    };
    const classroomRepo = {
      findOne: jest.fn().mockResolvedValue({
        id: 2,
        status: ClassroomStatus.AVAILABLE,
      }),
    };
    const classTeacherRepo = {
      findOne: jest.fn().mockResolvedValue({
        classId: 1,
        userId: 1,
      }),
      find: jest.fn().mockResolvedValue([]),
    };
    const rentalRepo = {
      createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder([])),
    };

    return {
      service: new SchedulesService(
        scheduleRepo as never,
        {} as never,
        classroomRepo as never,
        rentalRepo as never,
        classTeacherRepo as never,
        {} as never,
      ),
      scheduleRepo,
    };
  }

  it('create rejects reversed startTime/endTime before repository access', async () => {
    const { service, scheduleRepo } = makeMinimalService();

    await expect(
      service.create({ ...validDto, startTime: '14:00', endTime: '13:00' }),
    ).rejects.toThrow(BadRequestException);

    await expect(
      service.create({ ...validDto, startTime: '14:00', endTime: '13:00' }),
    ).rejects.toThrow('上课时间不能晚于下课时间');

    // Validation short-circuits before any repository call.
    expect(scheduleRepo.create).not.toHaveBeenCalled();
  });

  it('create allows startTime < endTime and proceeds through full create flow', async () => {
    const { service, scheduleRepo } = makeFullService();

    const savedSchedule = {
      id: 1,
      ...validDto,
      startTime: '09:00',
      endTime: '12:00',
      status: 'active',
    };
    scheduleRepo.create.mockReturnValue(savedSchedule);
    scheduleRepo.save.mockResolvedValue(savedSchedule);
    scheduleRepo.findOne.mockResolvedValue(savedSchedule);

    const result = await service.create({
      ...validDto,
      startTime: '09:00',
      endTime: '12:00',
    });

    // Should not throw during validation.
    expect(result).toMatchObject({ startTime: '09:00', endTime: '12:00' });
    expect(scheduleRepo.create).toHaveBeenCalled();
  });

  it('create rejects equal startTime/endTime before repository access', async () => {
    const { service, scheduleRepo } = makeMinimalService();

    await expect(
      service.create({ ...validDto, startTime: '09:00', endTime: '09:00' }),
    ).rejects.toThrow(BadRequestException);

    await expect(
      service.create({ ...validDto, startTime: '09:00', endTime: '09:00' }),
    ).rejects.toThrow('上课时间和下课时间不能相同');

    // Validation short-circuits before any repository call.
    expect(scheduleRepo.create).not.toHaveBeenCalled();
  });
});
