/// <reference types="jest" />
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';

// Mock nestjs-prisma 模块
jest.mock('nestjs-prisma', () => ({
  PrismaService: jest.fn(),
}));

import { PrismaService } from 'nestjs-prisma';
import { AgendaJobService } from './agendaJob.service';

// Mock PrismaService
const mockPrismaService = {
  agendaJob: {
    create: jest.fn(),
    createMany: jest.fn(),
    findMany: jest.fn(),
    findUnique: jest.fn(),
    update: jest.fn(),
    updateMany: jest.fn(),
    delete: jest.fn(),
    deleteMany: jest.fn(),
    count: jest.fn(),
    groupBy: jest.fn(),
    aggregate: jest.fn(),
  },
};

// Mock Logger
const mockLogger = {
  child: jest.fn().mockReturnThis(),
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
  debug: jest.fn(),
};

// Mock ConfigService
const mockConfigService = {
  get: jest.fn(),
};

describe('AgendaJobService', () => {
  let service: AgendaJobService;
  let prisma: typeof mockPrismaService;

  beforeEach(async () => {
    // 每个测试前重置所有 mock
    jest.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AgendaJobService,
        { provide: PrismaService, useValue: mockPrismaService },
        { provide: ConfigService, useValue: mockConfigService },
        { provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
      ],
    }).compile();

    service = module.get<AgendaJobService>(AgendaJobService);
    prisma = mockPrismaService;
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('createOne', () => {
    it('should create a single agendaJob record', async () => {
      const createArgs = {
        data: {
          name: 'test_name',
          priority: 0,
          nextRunAt: new Date(),
          type: 'normal',
          lockedAt: new Date(),
        },
      };
      const expectedResult = { id: 'test-id-123', ...createArgs.data };

      prisma.agendaJob.create.mockResolvedValue(expectedResult);

      const result = await service.createOne(createArgs);

      expect(prisma.agendaJob.create).toHaveBeenCalledWith(createArgs);
      expect(prisma.agendaJob.create).toHaveBeenCalledTimes(1);
      expect(result).toEqual(expectedResult);
    });

    it('should throw error when create fails', async () => {
      const createArgs = {
        data: {
          name: 'test_name',
          priority: 0,
          nextRunAt: new Date(),
          type: 'normal',
          lockedAt: new Date(),
        },
      };
      const error = new Error('Database connection failed');

      prisma.agendaJob.create.mockRejectedValue(error);

      await expect(service.createOne(createArgs)).rejects.toThrow(
        'Database connection failed',
      );
    });
  });

  describe('createMany', () => {
    it('should create multiple agendaJob records with skipDuplicates', async () => {
      const data = [
        {
          name: 'test_name',
          priority: 0,
          nextRunAt: new Date(),
          type: 'normal',
          lockedAt: new Date(),
        },
        {
          name: 'test_name',
          priority: 0,
          nextRunAt: new Date(),
          type: 'normal',
          lockedAt: new Date(),
        },
      ];
      const expectedResult = { count: 2 };

      prisma.agendaJob.createMany.mockResolvedValue(expectedResult);

      const result = await service.createMany(data);

      expect(prisma.agendaJob.createMany).toHaveBeenCalledWith({
        data,
        skipDuplicates: true,
      });
      expect(result).toEqual(expectedResult);
    });
  });

  describe('findMany', () => {
    it('should return entities with itemCount', async () => {
      const findArgs = {
        where: {},
        take: 10,
        skip: 0,
      };
      const mockEntities = [{ id: 'test-id-123' }, { id: 'test-id-123' }];

      prisma.agendaJob.count.mockResolvedValue(2);
      prisma.agendaJob.findMany.mockResolvedValue(mockEntities);

      const result = await service.findMany(findArgs);

      expect(prisma.agendaJob.count).toHaveBeenCalledWith({
        where: findArgs.where,
      });
      expect(prisma.agendaJob.findMany).toHaveBeenCalledWith(findArgs);
      expect(result).toEqual({
        itemCount: 2,
        entities: mockEntities,
      });
    });

    it('should return empty result when no records found', async () => {
      const findArgs = { where: {} };

      prisma.agendaJob.count.mockResolvedValue(0);
      prisma.agendaJob.findMany.mockResolvedValue([]);

      const result = await service.findMany(findArgs);

      expect(result).toEqual({ itemCount: 0, entities: [] });
    });
  });

  describe('findUnique', () => {
    it('should find a single agendaJob record by id', async () => {
      const id = 'test-id-123';
      const select = { id: true };
      const expectedResult = { id: 'test-id-123' };

      prisma.agendaJob.findUnique.mockResolvedValue(expectedResult);

      const result = await service.findUnique(id, select);

      expect(prisma.agendaJob.findUnique).toHaveBeenCalledWith({
        where: { id },
        select,
      });
      expect(result).toEqual(expectedResult);
    });

    it('should return null when record not found', async () => {
      prisma.agendaJob.findUnique.mockResolvedValue(null);

      const result = await service.findUnique('test-id-123', { id: true });

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

  describe('updateUnique', () => {
    it('should update a single agendaJob record', async () => {
      const id = 'test-id-123';
      const data = {
        name: 'test_name',
        priority: 0,
        nextRunAt: new Date(),
        type: 'normal',
        lockedAt: new Date(),
      };
      const expectedResult = { id: 'test-id-123', ...data };

      prisma.agendaJob.update.mockResolvedValue(expectedResult);

      const result = await service.updateUnique(id, data);

      expect(prisma.agendaJob.update).toHaveBeenCalledWith({
        where: { id },
        data,
      });
      expect(result).toEqual(expectedResult);
    });
  });

  describe('updateMany', () => {
    it('should update multiple agendaJob records', async () => {
      const updateArgs = {
        where: {},
        data: {
          name: 'test_name',
          priority: 0,
          nextRunAt: new Date(),
          type: 'normal',
          lockedAt: new Date(),
        },
      };
      const expectedResult = { count: 5 };

      prisma.agendaJob.updateMany.mockResolvedValue(expectedResult);

      const result = await service.updateMany(updateArgs);

      expect(prisma.agendaJob.updateMany).toHaveBeenCalledWith(updateArgs);
      expect(result).toEqual(expectedResult);
    });
  });

  describe('deleteUnique', () => {
    it('should delete a single agendaJob record and return count', async () => {
      const id = 'test-id-123';

      prisma.agendaJob.delete.mockResolvedValue({ id: 'test-id-123' });

      const result = await service.deleteUnique(id);

      expect(prisma.agendaJob.delete).toHaveBeenCalledWith({ where: { id } });
      expect(result).toEqual({ count: 1 });
    });
  });

  describe('deleteMany', () => {
    it('should delete multiple agendaJob records', async () => {
      const deleteArgs = { where: {} };
      const expectedResult = { count: 3 };

      prisma.agendaJob.deleteMany.mockResolvedValue(expectedResult);

      const result = await service.deleteMany(deleteArgs);

      expect(prisma.agendaJob.deleteMany).toHaveBeenCalledWith(deleteArgs);
      expect(result).toEqual(expectedResult);
    });
  });

  describe('count', () => {
    it('should count agendaJob records with filter', async () => {
      const countArgs = { where: {} };

      prisma.agendaJob.count.mockResolvedValue(10);

      const result = await service.count(countArgs);

      expect(prisma.agendaJob.count).toHaveBeenCalledWith(countArgs);
      expect(result).toBe(10);
    });

    it('should count all records when no filter provided', async () => {
      prisma.agendaJob.count.mockResolvedValue(100);

      const result = await service.count();

      expect(prisma.agendaJob.count).toHaveBeenCalledWith(undefined);
      expect(result).toBe(100);
    });
  });

  describe('groupBy', () => {
    it('should group agendaJob records by specified field', async () => {
      const groupByArgs = {
        by: ['id'],
        _count: { id: true },
      };
      const expectedResult = [{ id: 'test-id-123', _count: { id: 50 } }];

      prisma.agendaJob.groupBy.mockResolvedValue(expectedResult);

      const result = await service.groupBy(groupByArgs as any);

      expect(prisma.agendaJob.groupBy).toHaveBeenCalledWith(groupByArgs);
      expect(result).toEqual(expectedResult);
    });
  });

  describe('aggregate', () => {
    it('should aggregate agendaJob records', async () => {
      const aggregateArgs = {
        _count: true,
        _max: { id: true },
        _min: { id: true },
      } as const;
      const expectedResult = {
        _count: 100,
        _max: { id: 'test-id-123' },
        _min: { id: 'test-id-123' },
      };

      prisma.agendaJob.aggregate.mockResolvedValue(expectedResult);

      const result = await service.aggregate(aggregateArgs);

      expect(prisma.agendaJob.aggregate).toHaveBeenCalledWith(aggregateArgs);
      expect(result).toEqual(expectedResult);
    });
  });
});
