import { Test, TestingModule } from '@nestjs/testing';
import { CoursesService } from '../courses.service';
import { PrismaService } from '../../../prisma/prisma.service';

describe('CoursesService', () => {
  let service: CoursesService;
  let prisma: PrismaService;

  const mockPrisma = {
    course: {
      findMany: jest.fn(),
      findUnique: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      count: jest.fn(),
    },
    chapter: {
      findMany: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      deleteMany: jest.fn(),
    },
    lesson: {
      findMany: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      deleteMany: jest.fn(),
    },
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CoursesService,
        { provide: PrismaService, useValue: mockPrisma },
      ],
    }).compile();

    service = module.get<CoursesService>(CoursesService);
    prisma = module.get<PrismaService>(PrismaService);
    jest.clearAllMocks();
  });

  describe('findAll', () => {
    it('should return paginated courses with chapters/lessons', async () => {
      const mockCourses = [
        { id: 1, title: 'AI 入门', status: 'PUBLISHED', chapters: [{ id: 1, lessons: [{ id: 1 }] }] },
      ];
      mockPrisma.course.findMany.mockResolvedValue(mockCourses);
      mockPrisma.course.count.mockResolvedValue(1);

      const result = await service.findAll({ page: 1, pageSize: 20 });

      expect(result).toEqual({ items: mockCourses, total: 1, page: 1, pageSize: 20 });
      expect(mockPrisma.course.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { status: 'PUBLISHED', deletedAt: null },
          skip: 0,
          take: 20,
        })
      );
    });

    it('should filter by categoryId', async () => {
      mockPrisma.course.findMany.mockResolvedValue([]);
      mockPrisma.course.count.mockResolvedValue(0);

      await service.findAll({ categoryId: 3 });

      expect(mockPrisma.course.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({ categoryId: 3 }),
        })
      );
    });

    it('should filter free courses', async () => {
      mockPrisma.course.findMany.mockResolvedValue([]);
      mockPrisma.course.count.mockResolvedValue(0);

      await service.findAll({ isFree: true });

      expect(mockPrisma.course.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({ isFree: true }),
        })
      );
    });

    it('should apply pagination correctly', async () => {
      mockPrisma.course.findMany.mockResolvedValue([]);
      mockPrisma.course.count.mockResolvedValue(50);

      const result = await service.findAll({ page: 3, pageSize: 10 });

      expect(result.page).toBe(3);
      expect(result.pageSize).toBe(10);
      expect(mockPrisma.course.findMany).toHaveBeenCalledWith(
        expect.objectContaining({ skip: 20, take: 10 })
      );
    });
  });

  describe('findById', () => {
    it('should return course with chapters and lessons', async () => {
      const mockCourse = {
        id: 1,
        title: 'AI 入门',
        chapters: [{ id: 1, title: '第一章', lessons: [{ id: 1, title: '第一课' }] }],
      };
      mockPrisma.course.findUnique.mockResolvedValue(mockCourse);

      const result = await service.findById(1);

      expect(result).toEqual(mockCourse);
      expect(mockPrisma.course.findUnique).toHaveBeenCalledWith({
        where: { id: 1 },
        include: expect.objectContaining({
          chapters: expect.objectContaining({
            include: { lessons: expect.any(Object) },
          }),
        }),
      });
    });

    it('should return null for non-existent course', async () => {
      mockPrisma.course.findUnique.mockResolvedValue(null);

      const result = await service.findById(999);

      expect(result).toBeNull();
    });
  });

  describe('create', () => {
    it('should create a new course', async () => {
      const newCourse = { id: 2, title: '新课程', isFree: true };
      mockPrisma.course.create.mockResolvedValue(newCourse);

      const result = await service.create({ title: '新课程', isFree: true });

      expect(result).toEqual(newCourse);
      expect(mockPrisma.course.create).toHaveBeenCalledWith({
        data: { title: '新课程', isFree: true },
      });
    });

    it('should create course with all fields', async () => {
      mockPrisma.course.create.mockResolvedValue({ id: 3 });

      await service.create({
        title: '完整课程',
        description: '描述',
        categoryId: 1,
        price: 99,
        isFree: false,
        sortOrder: 5,
      });

      expect(mockPrisma.course.create).toHaveBeenCalledWith({
        data: {
          title: '完整课程',
          description: '描述',
          categoryId: 1,
          price: 99,
          isFree: false,
          sortOrder: 5,
        },
      });
    });
  });

  describe('update', () => {
    it('should update course fields', async () => {
      mockPrisma.course.update.mockResolvedValue({ id: 1, title: '更新标题' });

      const result = await service.update(1, { title: '更新标题' });

      expect(result).toEqual({ id: 1, title: '更新标题' });
      expect(mockPrisma.course.update).toHaveBeenCalledWith({
        where: { id: 1 },
        data: { title: '更新标题' },
      });
    });

    it('should strip chapters from course data update', async () => {
      mockPrisma.course.update.mockResolvedValue({ id: 1 });

      await service.update(1, { title: 'test', chapters: [{ title: 'ch1' }] });

      expect(mockPrisma.course.update).toHaveBeenCalledWith({
        where: { id: 1 },
        data: { title: 'test' },  // chapters stripped
      });
    });
  });

  describe('remove', () => {
    it('should soft-delete a course', async () => {
      mockPrisma.course.update.mockResolvedValue({ id: 1, deletedAt: new Date() });

      const result = await service.remove(1);

      expect(result).toBeDefined();
      expect(mockPrisma.course.update).toHaveBeenCalledWith({
        where: { id: 1 },
        data: { deletedAt: expect.any(Date) },
      });
    });
  });

  describe('updateWithChapters', () => {
    beforeEach(() => {
      mockPrisma.chapter.findMany.mockResolvedValue([]);
      mockPrisma.course.update.mockResolvedValue({ id: 1 });
    });

    it('should create new chapters and lessons', async () => {
      mockPrisma.chapter.findMany.mockResolvedValue([]);
      mockPrisma.chapter.create.mockResolvedValue({ id: 10 });
      mockPrisma.lesson.create.mockResolvedValue({ id: 20 });

      const result = await service.updateWithChapters(1, {
        title: '更新课程',
        chapters: [
          {
            title: '新章节',
            sortOrder: 1,
            lessons: [{ title: '新课时', sortOrder: 1, status: 'PUBLISHED' }],
          },
        ],
      });

      expect(result).toBeDefined();
      expect(mockPrisma.chapter.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            title: '新章节',
            courseId: 1,
          }),
        })
      );
    });

    it('should delete removed chapters', async () => {
      mockPrisma.chapter.findMany.mockResolvedValue([
        { id: 5, courseId: 1, title: '旧章节', sortOrder: 1 },
      ]);
      mockPrisma.lesson.deleteMany.mockResolvedValue({ count: 0 });
      mockPrisma.chapter.delete.mockResolvedValue({ id: 5 });

      await service.updateWithChapters(1, {
        title: '更新',
        chapters: [],  // no chapters = delete everything
      });

      expect(mockPrisma.chapter.delete).toHaveBeenCalledWith({ where: { id: 5 } });
    });

    it('should update existing chapters', async () => {
      mockPrisma.chapter.findMany.mockResolvedValue([
        { id: 5, courseId: 1, title: '旧章节', sortOrder: 1 },
      ]);
      mockPrisma.lesson.findMany.mockResolvedValue([]);
      mockPrisma.chapter.update.mockResolvedValue({ id: 5 });

      await service.updateWithChapters(1, {
        chapters: [{ id: 5, title: '更新章节', sortOrder: 2 }],
      });

      expect(mockPrisma.chapter.update).toHaveBeenCalledWith({
        where: { id: 5 },
        data: { title: '更新章节', sortOrder: 2 },
      });
    });
  });
});
