/**
 * 用户位置持久化核心服务单元测试
 * 
 * 功能描述：
 * - 测试用户位置持久化核心服务的所有功能
 * - 验证数据库操作和位置数据管理
 * - 确保错误处理和边界条件正确
 * - 提供完整的测试覆盖率
 * 
 * 测试范围：
 * - 位置数据持久化（保存/加载）
 * - 位置历史记录管理
 * - 批量操作和统计功能
 * - 异常情况处理
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-08
 * @lastModified 2026-01-08
 */

import { Test, TestingModule } from '@nestjs/testing';
import { UserPositionCore } from './user_position_core.service';
import { Position } from './position.interface';

describe('UserPositionCore', () => {
  let service: UserPositionCore;
  let mockUserProfilesService: any;

  beforeEach(async () => {
    // 创建用户档案服务的Mock
    mockUserProfilesService = {
      updatePosition: jest.fn(),
      findByUserId: jest.fn(),
      batchUpdateStatus: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserPositionCore,
        {
          provide: 'IUserProfilesService',
          useValue: mockUserProfilesService,
        },
      ],
    }).compile();

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

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

  describe('saveUserPosition', () => {
    it('应该成功保存用户位置到数据库', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      const position: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      mockUserProfilesService.updatePosition.mockResolvedValue(undefined);

      // Act
      await service.saveUserPosition(userId, position);

      // Assert
      expect(mockUserProfilesService.updatePosition).toHaveBeenCalledWith(
        BigInt(userId),
        {
          current_map: position.mapId,
          pos_x: position.x,
          pos_y: position.y
        }
      );
    });

    it('应该在用户ID为空时抛出异常', async () => {
      // Arrange
      const position: Position = {
        userId: 'test',
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      // Act & Assert
      await expect(service.saveUserPosition('', position)).rejects.toThrow('用户ID和位置信息不能为空');
    });

    it('应该在位置信息为空时抛出异常', async () => {
      // Act & Assert
      await expect(service.saveUserPosition('123', null as any)).rejects.toThrow('用户ID和位置信息不能为空');
    });

    it('应该在坐标不是数字时抛出异常', async () => {
      // Arrange
      const position: Position = {
        userId: 'test',
        x: 'invalid' as any,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      // Act & Assert
      await expect(service.saveUserPosition('123', position)).rejects.toThrow('位置坐标必须是数字类型');
    });

    it('应该在地图ID为空时抛出异常', async () => {
      // Arrange
      const position: Position = {
        userId: 'test',
        x: 100,
        y: 200,
        mapId: '',
        timestamp: Date.now(),
        metadata: {}
      };

      // Act & Assert
      await expect(service.saveUserPosition('123', position)).rejects.toThrow('地图ID不能为空');
    });

    it('应该处理数据库操作失败的情况', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      const position: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      mockUserProfilesService.updatePosition.mockRejectedValue(new Error('数据库连接失败'));

      // Act & Assert
      await expect(service.saveUserPosition(userId, position)).rejects.toThrow('数据库连接失败');
    });
  });

  describe('loadUserPosition', () => {
    it('应该成功从数据库加载用户位置', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      const mockUserProfile = {
        pos_x: 100,
        pos_y: 200,
        current_map: 'plaza',
        last_position_update: new Date(),
        status: 1,
        last_login_at: new Date()
      };

      mockUserProfilesService.findByUserId.mockResolvedValue(mockUserProfile);

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

      // Assert
      expect(result).toMatchObject({
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: mockUserProfile.last_position_update.getTime()
      });
      expect(result?.metadata).toMatchObject({
        status: 1,
        lastLogin: mockUserProfile.last_login_at.getTime()
      });
    });

    it('应该在用户ID为空时返回null', async () => {
      // Act
      const result = await service.loadUserPosition('');

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

    it('应该在用户档案不存在时返回null', async () => {
      // Arrange
      const userId = '999'; // 使用数字字符串
      mockUserProfilesService.findByUserId.mockResolvedValue(null);

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

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

    it('应该处理数据库查询失败的情况', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      mockUserProfilesService.findByUserId.mockRejectedValue(new Error('数据库查询失败'));

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

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

    it('应该使用默认值处理缺失的位置数据', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      const mockUserProfile = {
        pos_x: null,
        pos_y: null,
        current_map: null,
        last_position_update: null,
        status: 0,
        last_login_at: null
      };

      mockUserProfilesService.findByUserId.mockResolvedValue(mockUserProfile);

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

      // Assert
      expect(result).toMatchObject({
        userId,
        x: 0,
        y: 0,
        mapId: 'plaza'
      });
    });
  });

  describe('savePositionHistory', () => {
    it('应该成功保存位置历史记录', async () => {
      // Arrange
      const userId = 'test-user';
      const position: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };
      const sessionId = 'test-session';

      // Act & Assert
      await expect(service.savePositionHistory(userId, position, sessionId)).resolves.not.toThrow();
    });

    it('应该处理没有会话ID的情况', async () => {
      // Arrange
      const userId = 'test-user';
      const position: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      // Act & Assert
      await expect(service.savePositionHistory(userId, position)).resolves.not.toThrow();
    });
  });

  describe('getPositionHistory', () => {
    it('应该返回位置历史记录列表', async () => {
      // Arrange
      const userId = 'test-user';
      const limit = 5;

      // Act
      const result = await service.getPositionHistory(userId, limit);

      // Assert
      expect(Array.isArray(result)).toBe(true);
      expect(result).toHaveLength(0); // 当前版本返回空数组
    });

    it('应该使用默认限制数量', async () => {
      // Arrange
      const userId = 'test-user';

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

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

    it('应该处理查询失败的情况', async () => {
      // Arrange
      const userId = 'test-user';

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

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

  describe('batchUpdateUserStatus', () => {
    it('应该成功批量更新用户状态', async () => {
      // Arrange
      const userIds = ['123', '456', '789']; // 使用数字字符串
      const status = 1;
      const expectedUpdatedCount = 3;

      mockUserProfilesService.batchUpdateStatus.mockResolvedValue(expectedUpdatedCount);

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

      // Assert
      expect(result).toBe(expectedUpdatedCount);
      expect(mockUserProfilesService.batchUpdateStatus).toHaveBeenCalledWith(
        [BigInt('123'), BigInt('456'), BigInt('789')],
        status
      );
    });

    it('应该在用户ID列表为空时抛出异常', async () => {
      // Act & Assert
      await expect(service.batchUpdateUserStatus([], 1)).rejects.toThrow('用户ID列表不能为空');
    });

    it('应该在状态值无效时抛出异常', async () => {
      // Arrange
      const userIds = ['123']; // 使用数字字符串

      // Act & Assert
      await expect(service.batchUpdateUserStatus(userIds, -1)).rejects.toThrow('状态值必须是0-255之间的数字');
      await expect(service.batchUpdateUserStatus(userIds, 256)).rejects.toThrow('状态值必须是0-255之间的数字');
      await expect(service.batchUpdateUserStatus(userIds, 'invalid' as any)).rejects.toThrow('状态值必须是0-255之间的数字');
    });

    it('应该在用户ID无效时抛出异常', async () => {
      // Arrange
      const userIds = ['invalid-id'];
      const status = 1;

      // Act & Assert
      await expect(service.batchUpdateUserStatus(userIds, status)).rejects.toThrow('无效的用户ID: invalid-id');
    });

    it('应该处理数据库批量操作失败的情况', async () => {
      // Arrange
      const userIds = ['123', '456']; // 使用数字字符串
      const status = 1;

      mockUserProfilesService.batchUpdateStatus.mockRejectedValue(new Error('批量更新失败'));

      // Act & Assert
      await expect(service.batchUpdateUserStatus(userIds, status)).rejects.toThrow('批量更新失败');
    });
  });

  describe('cleanupExpiredPositions', () => {
    it('应该返回清理的记录数', async () => {
      // Arrange
      const expireTime = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // 7天前

      // Act
      const result = await service.cleanupExpiredPositions(expireTime);

      // Assert
      expect(typeof result).toBe('number');
      expect(result).toBeGreaterThanOrEqual(0);
    });

    it('应该处理清理操作失败的情况', async () => {
      // Arrange
      const expireTime = new Date();

      // Act
      const result = await service.cleanupExpiredPositions(expireTime);

      // Assert
      expect(result).toBe(0); // 错误时返回0
    });
  });

  describe('getUserPositionStats', () => {
    it('应该返回用户位置统计信息', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串
      const mockPosition: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      loadUserPositionSpy.mockResolvedValue(mockPosition);

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

      // Assert
      expect(result).toMatchObject({
        userId,
        hasCurrentPosition: true,
        currentPosition: mockPosition,
        historyCount: 0,
        totalMaps: 1
      });
    });

    it('应该处理用户没有位置数据的情况', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      loadUserPositionSpy.mockResolvedValue(null);

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

      // Assert
      expect(result).toMatchObject({
        userId,
        hasCurrentPosition: false,
        currentPosition: null,
        totalMaps: 0
      });
    });

    it('应该处理统计获取失败的情况', async () => {
      // Arrange
      const userId = '123'; // 使用数字字符串

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      loadUserPositionSpy.mockRejectedValue(new Error('统计失败'));

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

      // Assert
      expect(result).toMatchObject({
        userId,
        hasCurrentPosition: false,
        error: '统计失败'
      });
    });
  });

  describe('migratePositionData', () => {
    it('应该成功迁移位置数据', async () => {
      // Arrange
      const fromUserId = '123'; // 使用数字字符串
      const toUserId = '456'; // 使用数字字符串
      const sourcePosition: Position = {
        userId: fromUserId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      const saveUserPositionSpy = jest.spyOn(service, 'saveUserPosition');
      
      loadUserPositionSpy.mockResolvedValue(sourcePosition);
      saveUserPositionSpy.mockResolvedValue(undefined);

      // Act
      await service.migratePositionData(fromUserId, toUserId);

      // Assert
      expect(loadUserPositionSpy).toHaveBeenCalledWith(fromUserId);
      expect(saveUserPositionSpy).toHaveBeenCalledWith(toUserId, {
        ...sourcePosition,
        userId: toUserId
      });
    });

    it('应该在用户ID为空时抛出异常', async () => {
      // Act & Assert
      await expect(service.migratePositionData('', '456')).rejects.toThrow('源用户ID和目标用户ID不能为空');
      await expect(service.migratePositionData('123', '')).rejects.toThrow('源用户ID和目标用户ID不能为空');
    });

    it('应该在用户ID相同时抛出异常', async () => {
      // Act & Assert
      await expect(service.migratePositionData('123', '123')).rejects.toThrow('源用户ID和目标用户ID不能相同');
    });

    it('应该处理源用户没有位置数据的情况', async () => {
      // Arrange
      const fromUserId = '123'; // 使用数字字符串
      const toUserId = '456'; // 使用数字字符串

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      loadUserPositionSpy.mockResolvedValue(null);

      // Act & Assert
      await expect(service.migratePositionData(fromUserId, toUserId)).resolves.not.toThrow();
    });

    it('应该处理迁移操作失败的情况', async () => {
      // Arrange
      const fromUserId = '123'; // 使用数字字符串
      const toUserId = '456'; // 使用数字字符串
      const sourcePosition: Position = {
        userId: fromUserId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      const loadUserPositionSpy = jest.spyOn(service, 'loadUserPosition');
      const saveUserPositionSpy = jest.spyOn(service, 'saveUserPosition');
      
      loadUserPositionSpy.mockResolvedValue(sourcePosition);
      saveUserPositionSpy.mockRejectedValue(new Error('迁移失败'));

      // Act & Assert
      await expect(service.migratePositionData(fromUserId, toUserId)).rejects.toThrow('迁移失败');
    });
  });

  describe('边界条件测试', () => {
    it('应该处理极大的坐标值', async () => {
      // Arrange
      const userId = '123';
      const position: Position = {
        userId,
        x: Number.MAX_SAFE_INTEGER,
        y: Number.MAX_SAFE_INTEGER,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      mockUserProfilesService.updatePosition.mockResolvedValue(undefined);

      // Act & Assert
      await expect(service.saveUserPosition(userId, position)).resolves.not.toThrow();
    });

    it('应该处理极小的坐标值', async () => {
      // Arrange
      const userId = '123';
      const position: Position = {
        userId,
        x: Number.MIN_SAFE_INTEGER,
        y: Number.MIN_SAFE_INTEGER,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      mockUserProfilesService.updatePosition.mockResolvedValue(undefined);

      // Act & Assert
      await expect(service.saveUserPosition(userId, position)).resolves.not.toThrow();
    });

    it('应该处理大量用户的批量操作', async () => {
      // Arrange
      const userIds = Array.from({ length: 1000 }, (_, i) => (i + 1).toString()); // 使用数字字符串
      const status = 1;

      mockUserProfilesService.batchUpdateStatus.mockResolvedValue(1000);

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

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

    it('应该处理状态值边界', async () => {
      // Arrange
      const userIds = ['123']; // 使用数字字符串

      mockUserProfilesService.batchUpdateStatus.mockResolvedValue(1);

      // Act & Assert
      await expect(service.batchUpdateUserStatus(userIds, 0)).resolves.toBe(1);
      await expect(service.batchUpdateUserStatus(userIds, 255)).resolves.toBe(1);
    });
  });

  describe('性能测试', () => {
    it('应该在合理时间内完成位置保存', async () => {
      // Arrange
      const userId = '123';
      const position: Position = {
        userId,
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {}
      };

      mockUserProfilesService.updatePosition.mockResolvedValue(undefined);

      // Act
      const startTime = Date.now();
      await service.saveUserPosition(userId, position);
      const endTime = Date.now();

      // Assert
      expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
    });
  });
});