/**
 * 用户档案服务单元测试
 * 
 * 功能描述：
 * - 测试用户档案数据库服务的所有公共方法
 * - 覆盖正常情况、异常情况和边界情况
 * - 验证数据验证、错误处理和业务逻辑
 * - 确保与TypeORM和MySQL的正确集成
 * 
 * 测试覆盖：
 * - create(): 创建用户档案的各种场景
 * - findOne(): 根据ID查询档案的测试
 * - findByUserId(): 根据用户ID查询的测试
 * - findByMap(): 地图用户查询的测试
 * - update(): 档案信息更新的测试
 * - updatePosition(): 位置信息更新的测试
 * - batchUpdateStatus(): 批量状态更新的测试
 * - findAll(): 档案列表查询的测试
 * - count(): 档案数量统计的测试
 * - remove(): 档案删除的测试
 * - existsByUserId(): 档案存在性检查的测试
 * 
 * 最近修改：
 * - 2026-01-08: 功能新增 - 创建用户档案服务单元测试 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-08
 * @lastModified 2026-01-08
 */

import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { UserProfilesService } from './user_profiles.service';
import { UserProfiles } from './user_profiles.entity';
import { CreateUserProfileDto, UpdateUserProfileDto, UpdatePositionDto, QueryUserProfileDto } from './user_profiles.dto';

describe('UserProfilesService', () => {
  let service: UserProfilesService;
  let repository: jest.Mocked<Repository<UserProfiles>>;

  const mockUserProfile: UserProfiles = {
    id: BigInt(1),
    user_id: BigInt(100),
    bio: '测试用户简介',
    resume_content: '测试简历内容',
    tags: { skills: ['JavaScript', 'TypeScript'] },
    social_links: { github: 'https://github.com/testuser' },
    skin_id: 1001,
    current_map: 'plaza',
    pos_x: 100.5,
    pos_y: 200.3,
    status: 1,
    last_login_at: new Date(),
    last_position_update: new Date(),
  };

  beforeEach(async () => {
    const mockRepository = {
      save: jest.fn(),
      findOne: jest.fn(),
      find: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      count: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserProfilesService,
        {
          provide: getRepositoryToken(UserProfiles),
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get<UserProfilesService>(UserProfilesService);
    repository = module.get(getRepositoryToken(UserProfiles));
  });

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

  describe('create', () => {
    const createDto: CreateUserProfileDto = {
      user_id: BigInt(100),
      bio: '新用户简介',
      current_map: 'plaza',
      pos_x: 0,
      pos_y: 0,
      status: 0,
    };

    it('should create user profile successfully', async () => {
      // Arrange
      repository.findOne.mockResolvedValue(null); // 用户不存在
      repository.save.mockResolvedValue(mockUserProfile);

      // Act
      const result = await service.create(createDto);

      // Assert
      expect(result).toEqual(mockUserProfile);
      expect(repository.findOne).toHaveBeenCalledWith({
        where: { user_id: createDto.user_id }
      });
      expect(repository.save).toHaveBeenCalled();
    });

    it('should throw ConflictException when user already has profile', async () => {
      // Arrange
      repository.findOne.mockResolvedValue(mockUserProfile);

      // Act & Assert
      await expect(service.create(createDto)).rejects.toThrow(ConflictException);
      expect(repository.save).not.toHaveBeenCalled();
    });

    it('should handle database errors gracefully', async () => {
      // Arrange
      repository.findOne.mockResolvedValue(null);
      repository.save.mockRejectedValue(new Error('Database connection failed'));

      // Act & Assert
      await expect(service.create(createDto)).rejects.toThrow(BadRequestException);
    });

    it('should set default values correctly', async () => {
      // Arrange
      const minimalDto = { user_id: BigInt(100) } as CreateUserProfileDto;
      repository.findOne.mockResolvedValue(null);
      repository.save.mockImplementation((entity) => {
        // 模拟数据库保存后返回完整实体
        return Promise.resolve({
          ...entity,
          id: BigInt(1),
          last_position_update: new Date(),
        } as UserProfiles);
      });

      // Act
      const result = await service.create(minimalDto);

      // Assert
      expect(result.current_map).toBe('plaza');
      expect(result.pos_x).toBe(0);
      expect(result.pos_y).toBe(0);
      expect(result.status).toBe(0);
    });
  });

  describe('findOne', () => {
    it('should return user profile when found', async () => {
      // Arrange
      const profileId = BigInt(1);
      repository.findOne.mockResolvedValue(mockUserProfile);

      // Act
      const result = await service.findOne(profileId);

      // Assert
      expect(result).toEqual(mockUserProfile);
      expect(repository.findOne).toHaveBeenCalledWith({
        where: { id: profileId }
      });
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const profileId = BigInt(999);
      repository.findOne.mockResolvedValue(null);

      // Act & Assert
      await expect(service.findOne(profileId)).rejects.toThrow(NotFoundException);
    });
  });

  describe('findByUserId', () => {
    it('should return user profile when found', async () => {
      // Arrange
      const userId = BigInt(100);
      repository.findOne.mockResolvedValue(mockUserProfile);

      // Act
      const result = await service.findByUserId(userId);

      // Assert
      expect(result).toEqual(mockUserProfile);
      expect(repository.findOne).toHaveBeenCalledWith({
        where: { user_id: userId }
      });
    });

    it('should return null when profile not found', async () => {
      // Arrange
      const userId = BigInt(999);
      repository.findOne.mockResolvedValue(null);

      // Act
      const result = await service.findByUserId(userId);

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

  describe('findByMap', () => {
    it('should return users in specified map', async () => {
      // Arrange
      const mapId = 'plaza';
      const expectedProfiles = [mockUserProfile];
      repository.find.mockResolvedValue(expectedProfiles);

      // Act
      const result = await service.findByMap(mapId);

      // Assert
      expect(result).toEqual(expectedProfiles);
      expect(repository.find).toHaveBeenCalledWith({
        where: { current_map: mapId },
        take: 50,
        skip: 0,
        order: { last_position_update: 'DESC' }
      });
    });

    it('should filter by status when provided', async () => {
      // Arrange
      const mapId = 'plaza';
      const status = 1;
      repository.find.mockResolvedValue([mockUserProfile]);

      // Act
      await service.findByMap(mapId, status);

      // Assert
      expect(repository.find).toHaveBeenCalledWith({
        where: { current_map: mapId, status },
        take: 50,
        skip: 0,
        order: { last_position_update: 'DESC' }
      });
    });

    it('should handle pagination correctly', async () => {
      // Arrange
      const mapId = 'plaza';
      const limit = 10;
      const offset = 20;
      repository.find.mockResolvedValue([]);

      // Act
      await service.findByMap(mapId, undefined, limit, offset);

      // Assert
      expect(repository.find).toHaveBeenCalledWith({
        where: { current_map: mapId },
        take: limit,
        skip: offset,
        order: { last_position_update: 'DESC' }
      });
    });

    it('should return empty array on database error', async () => {
      // Arrange
      const mapId = 'plaza';
      repository.find.mockRejectedValue(new Error('Database error'));

      // Act
      const result = await service.findByMap(mapId);

      // Assert
      expect(result).toEqual([]);
    });
  });

  describe('update', () => {
    const updateDto: UpdateUserProfileDto = {
      bio: '更新后的简介',
      status: 2,
    };

    it('should update user profile successfully', async () => {
      // Arrange
      const profileId = BigInt(1);
      repository.findOne.mockResolvedValue(mockUserProfile);
      const updatedProfile = { ...mockUserProfile, ...updateDto };
      repository.save.mockResolvedValue(updatedProfile);

      // Act
      const result = await service.update(profileId, updateDto);

      // Assert
      expect(result).toEqual(updatedProfile);
      expect(repository.save).toHaveBeenCalled();
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const profileId = BigInt(999);
      repository.findOne.mockResolvedValue(null);

      // Act & Assert
      await expect(service.update(profileId, updateDto)).rejects.toThrow(NotFoundException);
      expect(repository.save).not.toHaveBeenCalled();
    });
  });

  describe('updatePosition', () => {
    const positionDto: UpdatePositionDto = {
      current_map: 'forest',
      pos_x: 150.5,
      pos_y: 250.3,
    };

    it('should update user position successfully', async () => {
      // Arrange
      const userId = BigInt(100);
      repository.findOne.mockResolvedValue(mockUserProfile);
      const updatedProfile = { ...mockUserProfile, ...positionDto };
      repository.save.mockResolvedValue(updatedProfile);

      // Act
      const result = await service.updatePosition(userId, positionDto);

      // Assert
      expect(result).toEqual(updatedProfile);
      expect(repository.findOne).toHaveBeenCalledWith({
        where: { user_id: userId }
      });
      expect(repository.save).toHaveBeenCalled();
    });

    it('should throw NotFoundException when user profile not found', async () => {
      // Arrange
      const userId = BigInt(999);
      repository.findOne.mockResolvedValue(null);

      // Act & Assert
      await expect(service.updatePosition(userId, positionDto)).rejects.toThrow(NotFoundException);
    });

    it('should update last_position_update timestamp', async () => {
      // Arrange
      const userId = BigInt(100);
      repository.findOne.mockResolvedValue(mockUserProfile);
      repository.save.mockImplementation((entity) => Promise.resolve(entity as UserProfiles));

      // Act
      await service.updatePosition(userId, positionDto);

      // Assert
      const savedEntity = repository.save.mock.calls[0][0];
      expect(savedEntity.last_position_update).toBeInstanceOf(Date);
    });
  });

  describe('batchUpdateStatus', () => {
    it('should update multiple users status successfully', async () => {
      // Arrange
      const userIds = [BigInt(100), BigInt(101), BigInt(102)];
      const status = 0;
      repository.update.mockResolvedValue({ affected: 3 } as any);

      // Act
      const result = await service.batchUpdateStatus(userIds, status);

      // Assert
      expect(result).toBe(3);
      expect(repository.update).toHaveBeenCalledWith(
        { user_id: { $in: userIds } },
        { status }
      );
    });

    it('should handle empty user list', async () => {
      // Arrange
      const userIds: bigint[] = [];
      const status = 0;
      repository.update.mockResolvedValue({ affected: 0 } as any);

      // Act
      const result = await service.batchUpdateStatus(userIds, status);

      // Assert
      expect(result).toBe(0);
    });

    it('should handle database errors', async () => {
      // Arrange
      const userIds = [BigInt(100)];
      const status = 0;
      repository.update.mockRejectedValue(new Error('Database error'));

      // Act & Assert
      await expect(service.batchUpdateStatus(userIds, status)).rejects.toThrow(BadRequestException);
    });
  });

  describe('findAll', () => {
    it('should return all profiles with default pagination', async () => {
      // Arrange
      const expectedProfiles = [mockUserProfile];
      repository.find.mockResolvedValue(expectedProfiles);

      // Act
      const result = await service.findAll();

      // Assert
      expect(result).toEqual(expectedProfiles);
      expect(repository.find).toHaveBeenCalledWith({
        where: {},
        take: 20,
        skip: 0,
        order: { last_position_update: 'DESC' }
      });
    });

    it('should filter by query parameters', async () => {
      // Arrange
      const queryDto: QueryUserProfileDto = {
        current_map: 'plaza',
        status: 1,
        limit: 10,
        offset: 5,
      };
      repository.find.mockResolvedValue([mockUserProfile]);

      // Act
      await service.findAll(queryDto);

      // Assert
      expect(repository.find).toHaveBeenCalledWith({
        where: { current_map: 'plaza', status: 1 },
        take: 10,
        skip: 5,
        order: { last_position_update: 'DESC' }
      });
    });
  });

  describe('count', () => {
    it('should return total count without conditions', async () => {
      // Arrange
      repository.count.mockResolvedValue(42);

      // Act
      const result = await service.count();

      // Assert
      expect(result).toBe(42);
      expect(repository.count).toHaveBeenCalledWith({ where: undefined });
    });

    it('should return filtered count with conditions', async () => {
      // Arrange
      const conditions = { status: 1 };
      repository.count.mockResolvedValue(15);

      // Act
      const result = await service.count(conditions);

      // Assert
      expect(result).toBe(15);
      expect(repository.count).toHaveBeenCalledWith({ where: conditions });
    });
  });

  describe('remove', () => {
    it('should delete user profile successfully', async () => {
      // Arrange
      const profileId = BigInt(1);
      repository.findOne.mockResolvedValue(mockUserProfile);
      repository.delete.mockResolvedValue({ affected: 1 } as any);

      // Act
      const result = await service.remove(profileId);

      // Assert
      expect(result).toEqual({
        affected: 1,
        message: `成功删除ID为 ${profileId} 的用户档案`
      });
      expect(repository.delete).toHaveBeenCalledWith({ id: profileId });
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const profileId = BigInt(999);
      repository.findOne.mockResolvedValue(null);

      // Act & Assert
      await expect(service.remove(profileId)).rejects.toThrow(NotFoundException);
      expect(repository.delete).not.toHaveBeenCalled();
    });
  });

  describe('existsByUserId', () => {
    it('should return true when user profile exists', async () => {
      // Arrange
      const userId = BigInt(100);
      repository.count.mockResolvedValue(1);

      // Act
      const result = await service.existsByUserId(userId);

      // Assert
      expect(result).toBe(true);
      expect(repository.count).toHaveBeenCalledWith({
        where: { user_id: userId }
      });
    });

    it('should return false when user profile does not exist', async () => {
      // Arrange
      const userId = BigInt(999);
      repository.count.mockResolvedValue(0);

      // Act
      const result = await service.existsByUserId(userId);

      // Assert
      expect(result).toBe(false);
    });
  });
});