/**
 * PositionsService Unit Tests (v2.1.1)
 * 
 * 测试岗位管理服务
 * 
 * 测试覆盖:
 * - 创建岗位
 * - 查询岗位列表（按职级排序）
 * - 查询岗位详情
 * - 更新岗位信息
 * - 删除岗位（带删除保护）
 * - 岗位代码唯一性验证
 * - 业务规则验证
 * 
 * 基于文档: 
 * - docs/modules/organization/02-user-journey.md (场景8: 岗位和职级管理)
 * - docs/modules/organization/03-architecture.md (组件3.5: 岗位管理系统)
 * 
 * @version v2.1.1
 */

import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException, BadRequestException } from '@nestjs/common';
import { PositionsService } from '@/modules/organization/positions/positions.service';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { CreatePositionDto, UpdatePositionDto, PositionQueryDto } from '@/modules/organization/positions/dto/position.dto';

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

  const mockPosition = {
    id: 'pos-test-001',
    code: 'ENG_L5',
    name: '高级工程师',
    description: '负责核心技术研发',
    level: 5,
    createdAt: new Date(),
    updatedAt: new Date(),
    deletedAt: null,
  };

  const mockPrismaService: any = {
    position: {
      findUnique: jest.fn(),
      findFirst: jest.fn(),
      findMany: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      count: jest.fn(),
    },
  };

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

    service = module.get<PositionsService>(PositionsService);
    prisma = module.get<PrismaService>(PrismaService);

    jest.clearAllMocks();
  });

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

  describe('create', () => {
    it('[测试场景 8.1.1] 应该成功创建岗位', async () => {
      const createDto: CreatePositionDto = {
        code: 'ENG_L5',
        name: '高级工程师',
        description: '负责核心技术研发',
        level: 5,
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);
      jest.spyOn(prisma.position, 'create').mockResolvedValue(mockPosition as any);

      const result = await service.create(createDto);

      expect(result).toEqual(mockPosition);
      expect(result.code).toBe('ENG_L5');
      expect(result.name).toBe('高级工程师');
      expect(result.level).toBe(5);
      expect(result.description).toBe('负责核心技术研发');

      expect(prisma.position.findUnique).toHaveBeenCalledWith({
        where: { code: createDto.code },
      });
      expect(prisma.position.create).toHaveBeenCalledWith({
        data: {
          name: createDto.name,
          code: createDto.code,
          level: createDto.level || 0,
          description: createDto.description,
        },
      });
    });

    it('[测试场景 8.1.2] 岗位代码重复应抛出异常', async () => {
      const createDto: CreatePositionDto = {
        code: 'ENG_L5',
        name: '高级工程师',
        level: 5,
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(mockPosition as any);

      await expect(service.create(createDto)).rejects.toThrow(BadRequestException);
      await expect(service.create(createDto)).rejects.toThrow('Position code already exists');

      expect(prisma.position.create).not.toHaveBeenCalled();
    });

    it('应该正确处理职级默认值', async () => {
      const createDto: CreatePositionDto = {
        code: 'ENG_L1',
        name: '初级工程师',
        // level 未提供，应该默认为 0
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);
      jest.spyOn(prisma.position, 'create').mockResolvedValue({
        ...mockPosition,
        code: 'ENG_L1',
        level: 0,
      } as any);

      const result = await service.create(createDto);

      expect(result.level).toBe(0);
      expect(prisma.position.create).toHaveBeenCalledWith({
        data: expect.objectContaining({
          level: 0,
        }),
      });
    });

    it('应该正确处理可选字段', async () => {
      const createDto: CreatePositionDto = {
        code: 'MGR_L5',
        name: '部门经理',
        level: 5,
        // description 为可选
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);
      jest.spyOn(prisma.position, 'create').mockResolvedValue({
        ...mockPosition,
        code: 'MGR_L5',
        name: '部门经理',
        description: null,
        createdAt: new Date(),
        updatedAt: new Date(),
      } as any);

      const result = await service.create(createDto);

      expect(result.code).toBe('MGR_L5');
      expect(result.description).toBeNull();
    });
  });

  describe('findAll', () => {
    const mockPositions = [
      {
        ...mockPosition,
        id: 'pos-1',
        code: 'ENG_L1',
        name: '初级工程师',
        level: 1,
        _count: { userMemberships: 3 },
      },
      {
        ...mockPosition,
        id: 'pos-2',
        code: 'ENG_L5',
        name: '高级工程师',
        level: 5,
        _count: { userMemberships: 2 },
      },
      {
        ...mockPosition,
        id: 'pos-3',
        code: 'MGR_L8',
        name: '部门经理',
        level: 8,
        _count: { userMemberships: 1 },
      },
    ];

    it('[测试场景 8.2.1] 应该返回所有岗位（按职级排序）', async () => {
      jest.spyOn(prisma.position, 'findMany').mockResolvedValue(mockPositions as any);

      const result = await service.findAll();

      expect(Array.isArray(result)).toBe(true);
      expect(result).toHaveLength(3);
      expect(result[0].level).toBe(1);
      expect(result[1].level).toBe(5);
      expect(result[2].level).toBe(8);

      expect(prisma.position.findMany).toHaveBeenCalledWith({
        where: { deletedAt: null },
        include: {
          _count: {
            select: {
              userMemberships: { where: { leftAt: null, user: { deletedAt: null } } },
            },
          },
        },
        orderBy: { level: 'asc' },
      });
    });

    it('应该支持关键字搜索（名称或代码）', async () => {
      const query: PositionQueryDto = {
        keyword: '工程师',
      };

      jest.spyOn(prisma.position, 'findMany').mockResolvedValue([
        mockPositions[0],
        mockPositions[1],
      ] as any);

      const result = await service.findAll(query);

      expect(result).toHaveLength(2);
      expect(prisma.position.findMany).toHaveBeenCalledWith({
        where: {
          deletedAt: null,
          OR: [
            { name: { contains: '工程师', mode: 'insensitive' } },
            { code: { contains: '工程师', mode: 'insensitive' } },
          ],
        },
        include: expect.any(Object),
        orderBy: { level: 'asc' },
      });
    });

    it('应该包含使用人数统计', async () => {
      jest.spyOn(prisma.position, 'findMany').mockResolvedValue(mockPositions as any);

      const result = await service.findAll();

      expect(result[0]._count.userMemberships).toBe(3);
      expect(result[1]._count.userMemberships).toBe(2);
      expect(result[2]._count.userMemberships).toBe(1);
    });
  });

  describe('findOne', () => {
    const mockPositionWithUsers = {
      ...mockPosition,
      userMemberships: [
        {
          id: 'ud-1',
          userId: 'user-1',
          positionId: mockPosition.id,
          departmentId: 'dept-1',
          user: {
            id: 'user-1',
            username: 'zhangsan',
            displayName: '张三',
            email: 'zhangsan@ff.com',
            status: 'ACTIVE',
          },
          department: {
            id: 'dept-1',
            name: '技术部',
          },
        },
      ],
    };

    it('[测试场景 8.3.1] 应该返回岗位详情（包含用户列表）', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(mockPositionWithUsers as any);

      const result = await service.findOne('pos-test-001');

      expect(result).toHaveProperty('id', 'pos-test-001');
      expect(result).toHaveProperty('code', 'ENG_L5');
      expect(result).toHaveProperty('userMemberships');
      expect(result.userMemberships).toHaveLength(1);
      expect(result.userMemberships[0]).toHaveProperty('user');
      expect(result.userMemberships[0]).toHaveProperty('department');

      expect(prisma.position.findUnique).toHaveBeenCalledWith({
        where: { id: 'pos-test-001', deletedAt: null },
        include: {
          userMemberships: {
            include: {
              user: {
                select: {
                  id: true,
                  username: true,
                  displayName: true,
                  email: true,
                  status: true,
                },
              },
              department: {
                select: { id: true, name: true },
              },
            },
          },
        },
      });
    });

    it('[测试场景 8.3.2] 岗位不存在应抛出异常', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);

      await expect(service.findOne('non-existent-id')).rejects.toThrow(NotFoundException);
      await expect(service.findOne('non-existent-id')).rejects.toThrow('Position not found');
    });
  });

  describe('update', () => {
    const updateDto: UpdatePositionDto = {
      name: '资深工程师',
      description: '更新后的描述',
      level: 6,
    };

    const updatedPosition = {
      ...mockPosition,
      ...updateDto,
      updatedAt: new Date(Date.now() + 1000),
    };

    it('[测试场景 8.4.1] 应该成功更新岗位信息', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(mockPosition as any);
      jest.spyOn(prisma.position, 'update').mockResolvedValue(updatedPosition as any);

      const result = await service.update('pos-test-001', updateDto);

      expect(result.name).toBe('资深工程师');
      expect(result.level).toBe(6);
      expect(result.updatedAt.getTime()).toBeGreaterThan(mockPosition.updatedAt.getTime());

      expect(prisma.position.update).toHaveBeenCalledWith({
        where: { id: 'pos-test-001' },
        data: updateDto,
      });
    });

    it('[测试场景 8.4.2] 更新不存在的岗位应抛出异常', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);

      await expect(service.update('non-existent-id', updateDto)).rejects.toThrow(
        NotFoundException
      );
      await expect(service.update('non-existent-id', updateDto)).rejects.toThrow(
        'Position not found'
      );

      expect(prisma.position.update).not.toHaveBeenCalled();
    });

    it('应该允许部分更新', async () => {
      const partialUpdateDto: UpdatePositionDto = {
        description: '仅更新描述',
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(mockPosition as any);
      jest.spyOn(prisma.position, 'update').mockResolvedValue({
        ...mockPosition,
        description: '仅更新描述',
        updatedAt: new Date(),
      } as any);

      const result = await service.update('pos-test-001', partialUpdateDto);

      expect(result.description).toBe('仅更新描述');
      expect(result.name).toBe(mockPosition.name);
      expect(result.level).toBe(mockPosition.level);
    });
  });

  describe('remove', () => {
    it('[测试场景 8.5.1] 应该成功软删除无用户的岗位', async () => {
      const positionWithoutUsers = {
        ...mockPosition,
        _count: { userMemberships: 0 },
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(positionWithoutUsers as any);
      jest.spyOn(prisma.position, 'update').mockResolvedValue({
        ...mockPosition,
        deletedAt: new Date(),
      } as any);

      const result = await service.remove('pos-test-001');

      expect(result).toEqual({ message: 'Position deleted successfully' });

      expect(prisma.position.update).toHaveBeenCalledWith({
        where: { id: 'pos-test-001' },
        data: { deletedAt: expect.any(Date) },
      });
    });

    it('[测试场景 8.5.2] 无法删除有用户使用的岗位', async () => {
      const positionWithUsers = {
        ...mockPosition,
        _count: { userMemberships: 5 },
      };

      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(positionWithUsers as any);

      await expect(service.remove('pos-test-001')).rejects.toThrow(BadRequestException);
      await expect(service.remove('pos-test-001')).rejects.toThrow('Cannot delete position with users');

      expect(prisma.position.update).not.toHaveBeenCalled();
    });

    it('删除不存在的岗位应抛出异常', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);

      await expect(service.remove('non-existent-id')).rejects.toThrow(NotFoundException);
      await expect(service.remove('non-existent-id')).rejects.toThrow('Position not found');

      expect(prisma.position.update).not.toHaveBeenCalled();
    });
  });

  describe('业务规则验证', () => {
    describe('岗位代码唯一性', () => {
      it('应该验证岗位代码在创建时唯一', async () => {
        const existingPosition = {
          ...mockPosition,
          code: 'ENG_L5',
        };

        jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(existingPosition as any);

        await expect(
          service.create({
            code: 'ENG_L5',
            name: '另一个高级工程师',
            level: 5,
          })
        ).rejects.toThrow(BadRequestException);
      });
    });

    describe('职级排序', () => {
      it('查询结果应该按职级升序排序', async () => {
        const unsortedPositions = [
          { ...mockPosition, id: 'pos-1', level: 8, code: 'MGR_L8' },
          { ...mockPosition, id: 'pos-2', level: 1, code: 'ENG_L1' },
          { ...mockPosition, id: 'pos-3', level: 5, code: 'ENG_L5' },
        ];

        jest.spyOn(prisma.position, 'findMany').mockResolvedValue(unsortedPositions as any);

        await service.findAll();

        expect(prisma.position.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            orderBy: { level: 'asc' },
          })
        );
      });
    });

    describe('删除保护', () => {
      it('应该统计活跃用户（leftAt为null）', async () => {
        jest.spyOn(prisma.position, 'findUnique').mockResolvedValue({
          ...mockPosition,
          _count: { userMemberships: 0 },
        } as any);

        await service.remove('pos-test-001');

        expect(prisma.position.findUnique).toHaveBeenCalledWith({
          where: { id: 'pos-test-001', deletedAt: null },
          include: {
            _count: {
              select: {
                userMemberships: { 
                  where: { 
                    leftAt: null, 
                    user: { deletedAt: null } 
                  } 
                },
              },
            },
          },
        });
      });

      it('应该只统计未删除的用户', async () => {
        jest.spyOn(prisma.position, 'findUnique').mockResolvedValue({
          ...mockPosition,
          _count: { userMemberships: 2 },
        } as any);

        await expect(service.remove('pos-test-001')).rejects.toThrow(BadRequestException);

        // 验证查询条件包含 user: { deletedAt: null }
        expect(prisma.position.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            include: {
              _count: {
                select: {
                  userMemberships: expect.objectContaining({
                    where: expect.objectContaining({
                      user: { deletedAt: null },
                    }),
                  }),
                },
              },
            },
          })
        );
      });
    });

    describe('软删除行为', () => {
      it('查询应该自动过滤已删除的岗位', async () => {
        jest.spyOn(prisma.position, 'findMany').mockResolvedValue([]);

        await service.findAll();

        expect(prisma.position.findMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              deletedAt: null,
            }),
          })
        );
      });

      it('查询单个岗位应该验证未删除', async () => {
        jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);

        await expect(service.findOne('pos-test-001')).rejects.toThrow(NotFoundException);

        expect(prisma.position.findUnique).toHaveBeenCalledWith(
          expect.objectContaining({
            where: { id: 'pos-test-001', deletedAt: null },
          })
        );
      });

      it('更新应该验证岗位未删除', async () => {
        jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(null);

        await expect(
          service.update('pos-test-001', { name: 'New Name' })
        ).rejects.toThrow(NotFoundException);

        expect(prisma.position.findUnique).toHaveBeenCalledWith({
          where: { id: 'pos-test-001', deletedAt: null },
        });
      });
    });
  });

  describe('性能与缓存考量', () => {
    it('查询列表应该包含使用统计以减少额外查询', async () => {
      jest.spyOn(prisma.position, 'findMany').mockResolvedValue([mockPosition] as any);

      await service.findAll();

      expect(prisma.position.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          include: {
            _count: expect.any(Object),
          },
        })
      );
    });

    it('查询详情应该包含用户关联以支持详情展示', async () => {
      jest.spyOn(prisma.position, 'findUnique').mockResolvedValue(mockPosition as any);

      await service.findOne('pos-test-001');

      expect(prisma.position.findUnique).toHaveBeenCalledWith(
        expect.objectContaining({
          include: {
            userMemberships: expect.any(Object),
          },
        })
      );
    });
  });
});

