/**
 * 用户档案内存服务单元测试
 * 
 * 功能描述：
 * - 测试用户档案内存服务的所有公共方法
 * - 覆盖正常情况、异常情况和边界情况
 * - 验证内存存储、ID生成和数据管理逻辑
 * - 确保与Map数据结构的正确集成
 * 
 * 测试覆盖：
 * - create(): 创建用户档案的各种场景
 * - findOne(): 根据ID查询档案的测试
 * - findByUserId(): 根据用户ID查询的测试
 * - findByMap(): 地图用户查询的测试
 * - update(): 档案信息更新的测试
 * - updatePosition(): 位置信息更新的测试
 * - batchUpdateStatus(): 批量状态更新的测试
 * - findAll(): 档案列表查询的测试
 * - count(): 档案数量统计的测试
 * - remove(): 档案删除的测试
 * - existsByUserId(): 档案存在性检查的测试
 * - clearAll(): 清空数据的测试
 * - getMemoryStats(): 内存统计的测试
 * 
 * 最近修改：
 * - 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 { ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { UserProfilesMemoryService } from './user_profiles_memory.service';
import { UserProfiles } from './user_profiles.entity';
import { CreateUserProfileDto, UpdateUserProfileDto, UpdatePositionDto, QueryUserProfileDto } from './user_profiles.dto';

describe('UserProfilesMemoryService', () => {
  let service: UserProfilesMemoryService;

  const createMockUserProfile = (overrides: Partial<UserProfiles> = {}): 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(),
    ...overrides,
  });

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UserProfilesMemoryService],
    }).compile();

    service = module.get<UserProfilesMemoryService>(UserProfilesMemoryService);
  });

  afterEach(async () => {
    // 清空内存数据，确保测试隔离
    await service.clearAll();
  });

  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 () => {
      // Act
      const result = await service.create(createDto);

      // Assert
      expect(result).toBeDefined();
      expect(result.user_id).toBe(createDto.user_id);
      expect(result.bio).toBe(createDto.bio);
      expect(result.current_map).toBe(createDto.current_map);
      expect(result.id).toBe(BigInt(1)); // 第一个ID应该是1
      expect(result.last_position_update).toBeInstanceOf(Date);
    });

    it('should generate unique IDs for multiple profiles', async () => {
      // Arrange
      const createDto1 = { ...createDto, user_id: BigInt(100) };
      const createDto2 = { ...createDto, user_id: BigInt(101) };

      // Act
      const result1 = await service.create(createDto1);
      const result2 = await service.create(createDto2);

      // Assert
      expect(result1.id).toBe(BigInt(1));
      expect(result2.id).toBe(BigInt(2));
      expect(result1.id).not.toBe(result2.id);
    });

    it('should throw ConflictException when user already has profile', async () => {
      // Arrange
      await service.create(createDto);

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

    it('should set default values correctly', async () => {
      // Arrange
      const minimalDto = { user_id: BigInt(100) } as CreateUserProfileDto;

      // 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);
      expect(result.bio).toBeNull();
      expect(result.resume_content).toBeNull();
    });

    it('should handle validation errors', async () => {
      // Arrange - 创建一个会导致验证失败的DTO
      const invalidDto = { 
        user_id: BigInt(100),
        current_map: '', // 空字符串应该导致验证失败
        pos_x: 0,
        pos_y: 0,
      } as CreateUserProfileDto;

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

  describe('findOne', () => {
    it('should return user profile when found', async () => {
      // Arrange
      const createDto: CreateUserProfileDto = {
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      };
      const created = await service.create(createDto);

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

      // Assert
      expect(result).toEqual(created);
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const nonExistentId = BigInt(999);

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

  describe('findByUserId', () => {
    it('should return user profile when found', async () => {
      // Arrange
      const createDto: CreateUserProfileDto = {
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      };
      const created = await service.create(createDto);

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

      // Assert
      expect(result).toEqual(created);
    });

    it('should return null when profile not found', async () => {
      // Act
      const result = await service.findByUserId(BigInt(999));

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

  describe('findByMap', () => {
    beforeEach(async () => {
      // 创建测试数据
      await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 100,
        pos_y: 100,
        status: 1,
      });
      await service.create({
        user_id: BigInt(101),
        current_map: 'plaza',
        pos_x: 200,
        pos_y: 200,
        status: 0,
      });
      await service.create({
        user_id: BigInt(102),
        current_map: 'forest',
        pos_x: 300,
        pos_y: 300,
        status: 1,
      });
    });

    it('should return users in specified map', async () => {
      // Act
      const result = await service.findByMap('plaza');

      // Assert
      expect(result).toHaveLength(2);
      expect(result.every(profile => profile.current_map === 'plaza')).toBe(true);
    });

    it('should filter by status when provided', async () => {
      // Act
      const result = await service.findByMap('plaza', 1);

      // Assert
      expect(result).toHaveLength(1);
      expect(result[0].status).toBe(1);
    });

    it('should handle pagination correctly', async () => {
      // Act
      const result = await service.findByMap('plaza', undefined, 1, 0);

      // Assert
      expect(result).toHaveLength(1);
    });

    it('should return empty array for non-existent map', async () => {
      // Act
      const result = await service.findByMap('non-existent');

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

    it('should sort by last_position_update descending', async () => {
      // Arrange
      const oldDate = new Date('2026-01-01');
      const newDate = new Date('2026-01-08');
      
      // 手动设置不同的更新时间
      const profiles = await service.findByMap('plaza');
      profiles[0].last_position_update = oldDate;
      profiles[1].last_position_update = newDate;

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

      // Assert
      expect(result[0].last_position_update?.getTime()).toBeGreaterThanOrEqual(
        result[1].last_position_update?.getTime() || 0
      );
    });
  });

  describe('update', () => {
    let profileId: bigint;

    beforeEach(async () => {
      const created = await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      });
      profileId = created.id;
    });

    it('should update user profile successfully', async () => {
      // Arrange
      const updateDto: UpdateUserProfileDto = {
        bio: '更新后的简介',
        status: 2,
      };

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

      // Assert
      expect(result.bio).toBe(updateDto.bio);
      expect(result.status).toBe(updateDto.status);
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const nonExistentId = BigInt(999);
      const updateDto: UpdateUserProfileDto = { bio: '测试' };

      // Act & Assert
      await expect(service.update(nonExistentId, updateDto)).rejects.toThrow(NotFoundException);
    });

    it('should only update provided fields', async () => {
      // Arrange
      const updateDto: UpdateUserProfileDto = { bio: '新简介' };
      const originalProfile = await service.findOne(profileId);

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

      // Assert
      expect(result.bio).toBe('新简介');
      expect(result.current_map).toBe(originalProfile.current_map); // 未更新的字段保持不变
    });
  });

  describe('updatePosition', () => {
    let userId: bigint;

    beforeEach(async () => {
      await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      });
      userId = BigInt(100);
    });

    it('should update user position successfully', async () => {
      // Arrange
      const positionDto: UpdatePositionDto = {
        current_map: 'forest',
        pos_x: 150.5,
        pos_y: 250.3,
      };

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

      // Assert
      expect(result.current_map).toBe(positionDto.current_map);
      expect(result.pos_x).toBe(positionDto.pos_x);
      expect(result.pos_y).toBe(positionDto.pos_y);
      expect(result.last_position_update).toBeInstanceOf(Date);
    });

    it('should throw NotFoundException when user profile not found', async () => {
      // Arrange
      const nonExistentUserId = BigInt(999);
      const positionDto: UpdatePositionDto = {
        current_map: 'forest',
        pos_x: 100,
        pos_y: 100,
      };

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

  describe('batchUpdateStatus', () => {
    beforeEach(async () => {
      // 创建多个用户档案
      await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.create({ user_id: BigInt(102), current_map: 'plaza', pos_x: 0, pos_y: 0 });
    });

    it('should update multiple users status successfully', async () => {
      // Arrange
      const userIds = [BigInt(100), BigInt(101), BigInt(102)];
      const newStatus = 2;

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

      // Assert
      expect(result).toBe(3);
      
      // 验证状态已更新
      const profile1 = await service.findByUserId(BigInt(100));
      const profile2 = await service.findByUserId(BigInt(101));
      const profile3 = await service.findByUserId(BigInt(102));
      
      expect(profile1?.status).toBe(newStatus);
      expect(profile2?.status).toBe(newStatus);
      expect(profile3?.status).toBe(newStatus);
    });

    it('should handle partial updates when some users not found', async () => {
      // Arrange
      const userIds = [BigInt(100), BigInt(999), BigInt(101)]; // 999不存在
      const newStatus = 2;

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

      // Assert
      expect(result).toBe(2); // 只有2个用户被更新
    });

    it('should handle empty user list', async () => {
      // Act
      const result = await service.batchUpdateStatus([], 1);

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

  describe('findAll', () => {
    beforeEach(async () => {
      await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
        status: 1,
      });
      await service.create({
        user_id: BigInt(101),
        current_map: 'forest',
        pos_x: 0,
        pos_y: 0,
        status: 0,
      });
    });

    it('should return all profiles with default pagination', async () => {
      // Act
      const result = await service.findAll();

      // Assert
      expect(result).toHaveLength(2);
    });

    it('should filter by query parameters', async () => {
      // Arrange
      const queryDto: QueryUserProfileDto = {
        current_map: 'plaza',
        status: 1,
      };

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

      // Assert
      expect(result).toHaveLength(1);
      expect(result[0].current_map).toBe('plaza');
      expect(result[0].status).toBe(1);
    });

    it('should handle pagination', async () => {
      // Arrange
      const queryDto: QueryUserProfileDto = {
        limit: 1,
        offset: 0,
      };

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

      // Assert
      expect(result).toHaveLength(1);
    });
  });

  describe('count', () => {
    beforeEach(async () => {
      await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0, status: 1 });
      await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0, status: 0 });
      await service.create({ user_id: BigInt(102), current_map: 'forest', pos_x: 0, pos_y: 0, status: 1 });
    });

    it('should return total count without conditions', async () => {
      // Act
      const result = await service.count();

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

    it('should return filtered count with conditions', async () => {
      // Act
      const result = await service.count({ status: 1 });

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

    it('should return zero for non-matching conditions', async () => {
      // Act
      const result = await service.count({ status: 999 });

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

  describe('remove', () => {
    let profileId: bigint;
    let userId: bigint;

    beforeEach(async () => {
      const created = await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      });
      profileId = created.id;
      userId = created.user_id;
    });

    it('should delete user profile successfully', async () => {
      // Act
      const result = await service.remove(profileId);

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

      // 验证档案已被删除
      await expect(service.findOne(profileId)).rejects.toThrow(NotFoundException);
      expect(await service.findByUserId(userId)).toBeNull();
    });

    it('should throw NotFoundException when profile not found', async () => {
      // Arrange
      const nonExistentId = BigInt(999);

      // Act & Assert
      await expect(service.remove(nonExistentId)).rejects.toThrow(NotFoundException);
    });
  });

  describe('existsByUserId', () => {
    it('should return true when user profile exists', async () => {
      // Arrange
      await service.create({
        user_id: BigInt(100),
        current_map: 'plaza',
        pos_x: 0,
        pos_y: 0,
      });

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

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

    it('should return false when user profile does not exist', async () => {
      // Act
      const result = await service.existsByUserId(BigInt(999));

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

  describe('clearAll', () => {
    it('should clear all data successfully', async () => {
      // Arrange
      await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0 });

      // Act
      await service.clearAll();

      // Assert
      const stats = service.getMemoryStats();
      expect(stats.profileCount).toBe(0);
      expect(stats.userIdMappingCount).toBe(0);
      expect(stats.currentId).toBe('1'); // ID重置为1
    });
  });

  describe('getMemoryStats', () => {
    it('should return correct memory statistics', async () => {
      // Arrange
      await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0 });

      // Act
      const stats = service.getMemoryStats();

      // Assert
      expect(stats.profileCount).toBe(2);
      expect(stats.userIdMappingCount).toBe(2);
      expect(stats.currentId).toBe('3'); // 下一个ID应该是3
    });

    it('should return zero stats for empty service', async () => {
      // Act
      const stats = service.getMemoryStats();

      // Assert
      expect(stats.profileCount).toBe(0);
      expect(stats.userIdMappingCount).toBe(0);
      expect(stats.currentId).toBe('1');
    });
  });

  describe('ID generation', () => {
    it('should generate sequential IDs', async () => {
      // Act
      const profile1 = await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      const profile2 = await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      const profile3 = await service.create({ user_id: BigInt(102), current_map: 'plaza', pos_x: 0, pos_y: 0 });

      // Assert
      expect(profile1.id).toBe(BigInt(1));
      expect(profile2.id).toBe(BigInt(2));
      expect(profile3.id).toBe(BigInt(3));
    });

    it('should continue ID sequence after deletion', async () => {
      // Arrange
      const profile1 = await service.create({ user_id: BigInt(100), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.create({ user_id: BigInt(101), current_map: 'plaza', pos_x: 0, pos_y: 0 });
      await service.remove(profile1.id);

      // Act
      const profile3 = await service.create({ user_id: BigInt(102), current_map: 'plaza', pos_x: 0, pos_y: 0 });

      // Assert
      expect(profile3.id).toBe(BigInt(3)); // ID不会重用
    });
  });
});