import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Mock } from 'vitest';
import { ClassesService } from '../../src/classes/classes.service.js';
import type { ClassesRepository } from '../../src/classes/classes.repository.js';
import { ValidationError, NotFoundError } from '../../src/shared/errors/application-error.js';

describe('ClassesService', () => {
  let service: ClassesService;
  let mockRepository: {
    create: Mock;
    findById: Mock;
    list: Mock;
    update: Mock;
    delete: Mock;
  };

  beforeEach(() => {
    mockRepository = {
      create: vi.fn(),
      findById: vi.fn(),
      list: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    };
    // 测试场景：mock 对象通过 unknown 转换为 ClassesRepository（规则允许测试中 as 转换）
    service = new ClassesService(mockRepository as unknown as ClassesRepository);
  });

  describe('create', () => {
    it('should create a class successfully', async () => {
      const dto = { name: 'Class 1A', gradeId: '550e8400-e29b-41d4-a716-446655440000' };
      const mockClass = {
        id: 'cls-1',
        name: 'Class 1A',
        gradeId: '550e8400-e29b-41d4-a716-446655440000',
        createdAt: new Date('2026-01-01'),
        updatedAt: new Date('2026-01-01'),
      };
      mockRepository.create.mockResolvedValue(mockClass);

      const result = await service.create(dto);

      expect(result.id).toBe('cls-1');
      expect(mockRepository.create).toHaveBeenCalledWith(expect.objectContaining(dto));
    });
  });

  describe('update', () => {
    it('should throw ValidationError when no fields provided', async () => {
      await expect(service.update('cls-1', {})).rejects.toThrow(ValidationError);
    });

    it('should update class successfully', async () => {
      const mockClass = {
        id: 'cls-1',
        name: 'Updated',
        gradeId: 'g1',
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      mockRepository.update.mockResolvedValue(mockClass);

      const result = await service.update('cls-1', { name: 'Updated' });

      expect(result.name).toBe('Updated');
      expect(mockRepository.update).toHaveBeenCalledWith('cls-1', { name: 'Updated' });
    });
  });

  describe('getById', () => {
    it('should throw NotFoundError when class not found', async () => {
      mockRepository.findById.mockResolvedValue(undefined);
      await expect(service.getById('not-exist')).rejects.toThrow(NotFoundError);
    });
  });

  describe('list', () => {
    it('should list classes without gradeId filter', async () => {
      mockRepository.list.mockResolvedValue([]);
      await service.list();
      expect(mockRepository.list).toHaveBeenCalledWith(undefined);
    });

    it('should list classes with gradeId filter', async () => {
      mockRepository.list.mockResolvedValue([]);
      await service.list('grade-1');
      expect(mockRepository.list).toHaveBeenCalledWith('grade-1');
    });
  });

  describe('delete', () => {
    it('should throw NotFoundError when class not found', async () => {
      mockRepository.findById.mockResolvedValue(undefined);
      await expect(service.delete('not-exist')).rejects.toThrow(NotFoundError);
    });

    it('should delete class', async () => {
      mockRepository.findById.mockResolvedValue({ id: 'cls-1' });
      mockRepository.delete.mockResolvedValue(undefined);
      await service.delete('cls-1');
      expect(mockRepository.delete).toHaveBeenCalledWith('cls-1');
    });
  });
});
