import { BadRequestException, NotFoundException } from '@nestjs/common';
import { ClassesService } from './classes.service';
import { ClassStudent } from '../entities';

function createService(
  classStudentRepo: Record<string, jest.Mock>,
  classRepo: Record<string, jest.Mock> = { findOne: jest.fn().mockResolvedValue({ id: 3 }) },
  studentRepo: Record<string, jest.Mock> = {
    find: jest.fn().mockResolvedValue([{ id: 8 }, { id: 9 }, { id: 10 }]),
  },
) {
  return new ClassesService(
    classRepo as never,
    classStudentRepo as never,
    {} as never,
    {} as never,
    {} as never,
    {} as never,
    studentRepo as never,
    {} as never,
  );
}

describe('ClassesService — student membership lifecycle', () => {
  it('marks an active membership as left instead of deleting it', async () => {
    const membership = {
      classId: 3,
      studentId: 8,
      status: 'active',
      leaveDate: null,
    } as ClassStudent;
    const repo = {
      findOne: jest.fn().mockResolvedValue(membership),
      save: jest.fn().mockImplementation(async (value: ClassStudent) => value),
    };
    const service = createService(repo);

    await service.removeStudent(3, 8);

    expect(membership.status).toBe('left');
    expect(membership.leaveDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
    expect(repo.save).toHaveBeenCalledWith(membership);
  });

  it('rejects removing an already-left membership', async () => {
    const repo = {
      findOne: jest.fn().mockResolvedValue({ status: 'left' }),
      save: jest.fn(),
    };
    const service = createService(repo);

    await expect(service.removeStudent(3, 8)).rejects.toBeInstanceOf(BadRequestException);
    expect(repo.save).not.toHaveBeenCalled();
  });

  it('rejects removing a student without a membership', async () => {
    const repo = {
      findOne: jest.fn().mockResolvedValue(null),
      save: jest.fn(),
    };
    const service = createService(repo);

    await expect(service.removeStudent(3, 8)).rejects.toBeInstanceOf(NotFoundException);
  });

  it('reactivates left memberships, creates new ones, and skips active ones', async () => {
    const left = {
      id: 1,
      classId: 3,
      studentId: 8,
      status: 'left',
      joinDate: '2026-01-01',
      leaveDate: '2026-02-01',
    } as ClassStudent;
    const active = { id: 2, classId: 3, studentId: 9, status: 'active' } as ClassStudent;
    const repo = {
      find: jest.fn().mockResolvedValue([left, active]),
      create: jest.fn().mockImplementation((value: Partial<ClassStudent>) => value),
      save: jest.fn().mockImplementation(async (value: ClassStudent[]) => value),
    };
    const service = createService(repo);

    const result = await service.addStudents(3, [8, 9, 10, 10]);

    expect(result).toEqual({ added: 2, skipped: 1 });
    expect(left).toMatchObject({ status: 'active', leaveDate: null });
    expect(left.joinDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
    expect(repo.create).toHaveBeenCalledWith(
      expect.objectContaining({ classId: 3, studentId: 10, status: 'active' }),
    );
    expect(repo.save).toHaveBeenCalledWith(
      expect.arrayContaining([left, expect.objectContaining({ studentId: 10 })]),
    );
  });
});
