import { Test, TestingModule } from '@nestjs/testing';
import { {{pascalName}}Service } from './{{kebabName}}.service';
import { PrismaService } from '@modules/common/prisma/prisma.service';
import { NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';

describe('{{pascalName}}Service', () => {
  let service: {{pascalName}}Service;
  let prismaService: PrismaService;

  const mock{{pascalName}} = {
    id: 'test-{{kebabName}}-id',
    {{#each fields}}
    {{#if (isCreateField this)}}
    {{name}}: {{#if (eq type 'string')}}'test {{name}}'{{else if (eq type 'number')}}123{{else if (eq type 'boolean')}}true{{else if (eq type 'Date')}}new Date(){{else}}'test'{{/if}},
    {{/if}}
    {{/each}}
    createdAt: new Date(),
    updatedAt: new Date(),
    {{#each relations}}
    {{#if isList}}
    {{name}}: [],
    {{else}}
    {{name}}: {
      id: 'test-{{kebabCase relatedModel}}-id',
      name: 'Test {{relatedModel}}'
    },
    {{/if}}
    {{/each}}
  };

  const mockPrismaService = {
    {{camelName}}: {
      create: jest.fn(),
      findMany: jest.fn(),
      count: jest.fn(),
      findUnique: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      createMany: jest.fn(),
      updateMany: jest.fn(),
      deleteMany: jest.fn(),
    },
    $transaction: jest.fn(),
  };

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

    service = module.get<{{pascalName}}Service>({{pascalName}}Service);
    prismaService = module.get<PrismaService>(PrismaService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

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

  describe('create', () => {
    const create{{pascalName}}Dto = {
      {{#each fields}}
      {{#if (isCreateField this)}}
      {{name}}: {{#if (eq type 'string')}}'test {{name}}'{{else if (eq type 'number')}}123{{else if (eq type 'boolean')}}true{{else if (eq type 'Date')}}new Date(){{else}}'test'{{/if}},
      {{/if}}
      {{/each}}
    };

    it('should create a {{camelName}} successfully', async () => {
      mockPrismaService.{{camelName}}.create.mockResolvedValue(mock{{pascalName}});

      const result = await service.create(create{{pascalName}}Dto);

      expect(result).toEqual(mock{{pascalName}});
      expect(mockPrismaService.{{camelName}}.create).toHaveBeenCalledWith({
        data: create{{pascalName}}Dto,
        include: {
          {{#each relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });

    it('should throw BadRequestException when {{camelName}} already exists', async () => {
      const error = new PrismaClientKnownRequestError('', {
        code: 'P2002',
        clientVersion: '2.0.0',
      });
      mockPrismaService.{{camelName}}.create.mockRejectedValue(error);

      await expect(service.create(create{{pascalName}}Dto)).rejects.toThrow(
        new BadRequestException('{{name}}已存在'),
      );
    });

    it('should throw BadRequestException when related resource not found', async () => {
      const error = new PrismaClientKnownRequestError('', {
        code: 'P2003',
        clientVersion: '2.0.0',
      });
      mockPrismaService.{{camelName}}.create.mockRejectedValue(error);

      await expect(service.create(create{{pascalName}}Dto)).rejects.toThrow(
        new BadRequestException('关联的资源不存在'),
      );
    });
  });

  describe('findAll', () => {
    const queryDto = {
      page: 1,
      pageSize: 10,
      sortBy: 'createdAt',
      sortOrder: 'desc' as const,
      {{#each fields}}
      {{#if (and (isQueryField this) (eq type 'string'))}}
      {{name}}: 'test',
      {{/if}}
      {{/each}}
    };

    it('should return paginated {{camelName}}s', async () => {
      const {{camelName}}s = [mock{{pascalName}}];
      mockPrismaService.{{camelName}}.findMany.mockResolvedValue({{camelName}}s);
      mockPrismaService.{{camelName}}.count.mockResolvedValue(1);

      const result = await service.findAll(queryDto);

      expect(result).toEqual({
        data: {{camelName}}s,
        total: 1,
        page: 1,
        pageSize: 10,
        totalPages: 1,
      });
      expect(mockPrismaService.{{camelName}}.findMany).toHaveBeenCalledWith({
        where: {
          {{#each fields}}
          {{#if (and (isQueryField this) (eq type 'string'))}}
          {{name}}: {
            contains: 'test',
            mode: 'insensitive',
          },
          {{/if}}
          {{/each}}
        },
        orderBy: { createdAt: 'desc' },
        skip: 0,
        take: 10,
        include: {
          {{#each relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });

    it('should handle empty results', async () => {
      mockPrismaService.{{camelName}}.findMany.mockResolvedValue([]);
      mockPrismaService.{{camelName}}.count.mockResolvedValue(0);

      const result = await service.findAll(queryDto);

      expect(result).toEqual({
        data: [],
        total: 0,
        page: 1,
        pageSize: 10,
        totalPages: 0,
      });
    });
  });

  describe('findOne', () => {
    it('should return a {{camelName}} by id', async () => {
      mockPrismaService.{{camelName}}.findUnique.mockResolvedValue(mock{{pascalName}});

      const result = await service.findOne({{#if hasCompositeId}}{{#each compositeIdFields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'test-{{kebabName}}-id'{{/if}});

      expect(result).toEqual(mock{{pascalName}});
      expect(mockPrismaService.{{camelName}}.findUnique).toHaveBeenCalledWith({
        where: { {{#if hasCompositeId}}{{compositeIdWhere compositeIdFields 'test-'}}{{else}}id: 'test-{{kebabName}}-id'{{/if}} },
        include: {
          {{#each relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });

    it('should throw NotFoundException when {{camelName}} not found', async () => {
      mockPrismaService.{{camelName}}.findUnique.mockResolvedValue(null);

      await expect(service.findOne({{#if hasCompositeId}}{{#each compositeIdFields}}'non-existent'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'non-existent-id'{{/if}})).rejects.toThrow(
        new NotFoundException(`{{name}}{{#if hasCompositeId}}{{#each compositeIdFields}} {{this}}: non-existent{{#unless @last}},{{/unless}}{{/each}}{{else}} ID: non-existent-id{{/if}} 不存在`),
      );
    });
  });

  {{#each uniqueFields}}
  describe('findBy{{pascalCase name}}', () => {
    it('should return a {{../camelName}} by {{name}}', async () => {
      mockPrismaService.{{../camelName}}.findUnique.mockResolvedValue(mock{{../pascalName}});

      const result = await service.findBy{{pascalCase name}}({{#each fields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}});

      expect(result).toEqual(mock{{../pascalName}});
      expect(mockPrismaService.{{../camelName}}.findUnique).toHaveBeenCalledWith({
        where: {
          {{#if isComposite}}
          {{name}}: {
            {{#each fields}}
            {{this}}: 'test-{{this}}',
            {{/each}}
          }
          {{else}}
          {{#each fields}}{{this}}: 'test-{{this}}'{{/each}}
          {{/if}}
        },
        include: {
          {{#each ../relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });

    it('should throw NotFoundException when {{../camelName}} not found by {{name}}', async () => {
      mockPrismaService.{{../camelName}}.findUnique.mockResolvedValue(null);

      await expect(service.findBy{{pascalCase name}}({{#each fields}}'non-existent'{{#unless @last}}, {{/unless}}{{/each}})).rejects.toThrow(
        new NotFoundException(`{{../name}} with {{#each fields}}{{this}}: non-existent{{#unless @last}}, {{/unless}}{{/each}} 不存在`),
      );
    });
  });

  {{/each}}

  {{#each indexes}}
  describe('findBy{{pascalCase name}}', () => {
    it('should return {{../camelName}}s by {{name}} index', async () => {
      const mock{{../pascalName}}s = [mock{{../pascalName}}];
      mockPrismaService.{{../camelName}}.findMany.mockResolvedValue(mock{{../pascalName}}s);

      const result = await service.findBy{{pascalCase name}}({{#each fields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}});

      expect(result).toEqual(mock{{../pascalName}}s);
      expect(mockPrismaService.{{../camelName}}.findMany).toHaveBeenCalledWith({
        where: {
          {{#if isComposite}}
          AND: [
            {{#each fields}}
            { {{this}}: 'test-{{this}}' },
            {{/each}}
          ]
          {{else}}
          {{#each fields}}{{this}}: 'test-{{this}}'{{/each}}
          {{/if}}
        },
        include: {
          {{#each ../relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });
  });

  {{/each}}

  describe('update', () => {
    const update{{pascalName}}Dto = {
      {{#each fields}}
      {{#if (and (isUpdateField this) (eq @index 0))}}
      {{name}}: 'Updated {{name}}',
      {{/if}}
      {{/each}}
    };

    it('should update a {{camelName}} successfully', async () => {
      const updated{{pascalName}} = { ...mock{{pascalName}}, ...update{{pascalName}}Dto };
      mockPrismaService.{{camelName}}.update.mockResolvedValue(updated{{pascalName}});

      const result = await service.update({{#if hasCompositeId}}{{#each compositeIdFields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'test-{{kebabName}}-id'{{/if}}, update{{pascalName}}Dto);

      expect(result).toEqual(updated{{pascalName}});
      expect(mockPrismaService.{{camelName}}.update).toHaveBeenCalledWith({
        where: { {{#if hasCompositeId}}{{compositeIdWhere compositeIdFields 'test-'}}{{else}}id: 'test-{{kebabName}}-id'{{/if}} },
        data: update{{pascalName}}Dto,
        include: {
          {{#each relations}}
          {{name}}: true,
          {{/each}}
        },
      });
    });

    it('should throw NotFoundException when {{camelName}} not found', async () => {
      const error = new PrismaClientKnownRequestError('', {
        code: 'P2025',
        clientVersion: '2.0.0',
      });
      mockPrismaService.{{camelName}}.update.mockRejectedValue(error);

      await expect(
        service.update({{#if hasCompositeId}}{{#each compositeIdFields}}'non-existent'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'non-existent-id'{{/if}}, update{{pascalName}}Dto),
      ).rejects.toThrow(
        new NotFoundException(`{{name}}{{#if hasCompositeId}}{{#each compositeIdFields}} {{this}}: non-existent{{#unless @last}},{{/unless}}{{/each}}{{else}} ID: non-existent-id{{/if}} 不存在`),
      );
    });
  });

  describe('remove', () => {
    it('should delete a {{camelName}} successfully', async () => {
      mockPrismaService.{{camelName}}.delete.mockResolvedValue(mock{{pascalName}});

      await expect(service.remove({{#if hasCompositeId}}{{#each compositeIdFields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'test-{{kebabName}}-id'{{/if}})).resolves.toBeUndefined();
      expect(mockPrismaService.{{camelName}}.delete).toHaveBeenCalledWith({
        where: { {{#if hasCompositeId}}{{compositeIdWhere compositeIdFields 'test-'}}{{else}}id: 'test-{{kebabName}}-id'{{/if}} },
      });
    });

    it('should throw NotFoundException when {{camelName}} not found', async () => {
      const error = new PrismaClientKnownRequestError('', {
        code: 'P2025',
        clientVersion: '2.0.0',
      });
      mockPrismaService.{{camelName}}.delete.mockRejectedValue(error);

      await expect(service.remove({{#if hasCompositeId}}{{#each compositeIdFields}}'non-existent'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'non-existent-id'{{/if}})).rejects.toThrow(
        new NotFoundException(`{{name}}{{#if hasCompositeId}}{{#each compositeIdFields}} {{this}}: non-existent{{#unless @last}},{{/unless}}{{/each}}{{else}} ID: non-existent-id{{/if}} 不存在`),
      );
    });

    it('should throw BadRequestException when {{camelName}} has dependencies', async () => {
      const error = new PrismaClientKnownRequestError('', {
        code: 'P2003',
        clientVersion: '2.0.0',
      });
      mockPrismaService.{{camelName}}.delete.mockRejectedValue(error);

      await expect(service.remove({{#if hasCompositeId}}{{#each compositeIdFields}}'test-{{this}}'{{#unless @last}}, {{/unless}}{{/each}}{{else}}'test-{{kebabName}}-id'{{/if}})).rejects.toThrow(
        new BadRequestException('无法删除，存在关联的资源'),
      );
    });
  });

  describe('batch operations', () => {
    describe('createMany', () => {
      it('should create multiple {{camelName}}s', async () => {
        const {{camelName}}s = [
          create{{pascalName}}Dto,
          create{{pascalName}}Dto,
        ];
        const expectedResult = { count: 2 };
        mockPrismaService.{{camelName}}.createMany.mockResolvedValue(expectedResult);

        const result = await service.createMany({{camelName}}s);

        expect(result).toEqual(expectedResult);
        expect(mockPrismaService.{{camelName}}.createMany).toHaveBeenCalledWith({
          data: {{camelName}}s,
          skipDuplicates: true,
        });
      });
    });

    describe('updateMany', () => {
      it('should update multiple {{camelName}}s', async () => {
        const ids = ['id1', 'id2'];
        const updateData = { name: 'Updated {{pascalName}} Name' };
        const expectedResult = { count: 2 };
        mockPrismaService.{{camelName}}.updateMany.mockResolvedValue(expectedResult);

        const result = await service.updateMany(ids, updateData);

        expect(result).toEqual(expectedResult);
        expect(mockPrismaService.{{camelName}}.updateMany).toHaveBeenCalledWith({
          where: { id: { in: ids } },
          data: updateData,
        });
      });
    });

    describe('removeMany', () => {
      it('should delete multiple {{camelName}}s', async () => {
        const ids = ['id1', 'id2'];
        const expectedResult = { count: 2 };
        mockPrismaService.{{camelName}}.deleteMany.mockResolvedValue(expectedResult);

        const result = await service.removeMany(ids);

        expect(result).toEqual(expectedResult);
        expect(mockPrismaService.{{camelName}}.deleteMany).toHaveBeenCalledWith({
          where: { id: { in: ids } },
        });
      });
    });
  });
});