/**
 * 位置广播业务服务单元测试
 * 
 * 功能描述：
 * - 测试位置广播业务服务的核心功能
 * - 验证业务逻辑的正确性和异常处理
 * - 确保服务间的正确协调和数据流转
 * - 提供完整的测试覆盖率
 * 
 * 测试范围：
 * - 用户加入/离开会话的业务逻辑
 * - 位置更新和广播功能
 * - 数据验证和错误处理
 * - 服务间的依赖调用
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-08
 * @lastModified 2026-01-08
 */

import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { LocationBroadcastService, JoinSessionRequest, PositionUpdateRequest } from './location_broadcast.service';
import { Position } from '../../../core/location_broadcast_core/position.interface';
import { SessionUser, SessionUserStatus } from '../../../core/location_broadcast_core/session.interface';

describe('LocationBroadcastService', () => {
  let service: LocationBroadcastService;
  let mockLocationBroadcastCore: any;
  let mockUserPositionCore: any;

  beforeEach(async () => {
    // 创建模拟的核心服务
    mockLocationBroadcastCore = {
      addUserToSession: jest.fn(),
      removeUserFromSession: jest.fn(),
      getSessionUsers: jest.fn(),
      getSessionPositions: jest.fn(),
      setUserPosition: jest.fn(),
      getUserPosition: jest.fn(),
      cleanupUserData: jest.fn(),
      getMapPositions: jest.fn(), // 添加缺失的方法
    };

    mockUserPositionCore = {
      saveUserPosition: jest.fn(),
      savePositionHistory: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        LocationBroadcastService,
        {
          provide: 'ILocationBroadcastCore',
          useValue: mockLocationBroadcastCore,
        },
        {
          provide: 'IUserPositionCore',
          useValue: mockUserPositionCore,
        },
      ],
    }).compile();

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

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

  describe('joinSession', () => {
    const mockJoinRequest: JoinSessionRequest = {
      userId: 'user123',
      sessionId: 'session456',
      socketId: 'socket789',
      initialPosition: {
        mapId: 'plaza',
        x: 100,
        y: 200,
      },
    };

    const mockSessionUsers: SessionUser[] = [
      {
        userId: 'user123',
        socketId: 'socket789',
        joinedAt: Date.now(),
        lastSeen: Date.now(),
        status: SessionUserStatus.ONLINE,
        metadata: {},
      },
    ];

    const mockPositions: Position[] = [
      {
        userId: 'user123',
        x: 100,
        y: 200,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {},
      },
    ];

    it('应该成功处理用户加入会话', async () => {
      // 准备模拟数据
      mockLocationBroadcastCore.addUserToSession.mockResolvedValue(undefined);
      mockLocationBroadcastCore.setUserPosition.mockResolvedValue(undefined);
      mockLocationBroadcastCore.getSessionUsers.mockResolvedValue(mockSessionUsers);
      mockLocationBroadcastCore.getSessionPositions.mockResolvedValue(mockPositions);

      // 执行测试
      const result = await service.joinSession(mockJoinRequest);

      // 验证结果
      expect(result.success).toBe(true);
      expect(result.session).toBeDefined();
      expect(result.users).toEqual(mockSessionUsers);
      expect(result.positions).toEqual(mockPositions);
      expect(result.message).toBe('成功加入会话');

      // 验证核心服务调用
      expect(mockLocationBroadcastCore.addUserToSession).toHaveBeenCalledWith(
        mockJoinRequest.sessionId,
        mockJoinRequest.userId,
        mockJoinRequest.socketId,
      );
      expect(mockLocationBroadcastCore.setUserPosition).toHaveBeenCalledWith(
        mockJoinRequest.userId,
        expect.objectContaining({
          userId: mockJoinRequest.userId,
          x: mockJoinRequest.initialPosition!.x,
          y: mockJoinRequest.initialPosition!.y,
          mapId: mockJoinRequest.initialPosition!.mapId,
        }),
      );
    });

    it('应该在没有初始位置时成功加入会话', async () => {
      const requestWithoutPosition = { ...mockJoinRequest };
      delete requestWithoutPosition.initialPosition;

      mockLocationBroadcastCore.addUserToSession.mockResolvedValue(undefined);
      mockLocationBroadcastCore.getSessionUsers.mockResolvedValue(mockSessionUsers);
      mockLocationBroadcastCore.getSessionPositions.mockResolvedValue([]);

      const result = await service.joinSession(requestWithoutPosition);

      expect(result.success).toBe(true);
      expect(mockLocationBroadcastCore.setUserPosition).not.toHaveBeenCalled();
    });

    it('应该在参数验证失败时抛出异常', async () => {
      const invalidRequest = { ...mockJoinRequest, userId: '' };

      await expect(service.joinSession(invalidRequest)).rejects.toThrow(BadRequestException);
    });

    it('应该在核心服务调用失败时抛出异常', async () => {
      mockLocationBroadcastCore.addUserToSession.mockRejectedValue(new Error('核心服务错误'));

      await expect(service.joinSession(mockJoinRequest)).rejects.toThrow('核心服务错误');
    });
  });

  describe('leaveSession', () => {
    it('应该成功处理用户离开会话', async () => {
      const mockPosition: Position = {
        userId: 'user123',
        x: 150,
        y: 250,
        mapId: 'plaza',
        timestamp: Date.now(),
        metadata: {},
      };

      mockLocationBroadcastCore.getUserPosition.mockResolvedValue(mockPosition);
      mockUserPositionCore.saveUserPosition.mockResolvedValue(undefined);
      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);

      const result = await service.leaveSession('user123', 'session456', 'user_left');

      expect(result).toBe(true);
      expect(mockLocationBroadcastCore.getUserPosition).toHaveBeenCalledWith('user123');
      expect(mockUserPositionCore.saveUserPosition).toHaveBeenCalledWith('user123', mockPosition);
      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalledWith('session456', 'user123');
    });

    it('应该在用户没有位置时仍能成功离开会话', async () => {
      mockLocationBroadcastCore.getUserPosition.mockResolvedValue(null);
      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);

      const result = await service.leaveSession('user123', 'session456');

      expect(result).toBe(true);
      expect(mockUserPositionCore.saveUserPosition).not.toHaveBeenCalled();
    });

    it('应该在参数为空时抛出异常', async () => {
      await expect(service.leaveSession('', 'session456')).rejects.toThrow(BadRequestException);
      await expect(service.leaveSession('user123', '')).rejects.toThrow(BadRequestException);
    });
  });

  describe('updatePosition', () => {
    const mockUpdateRequest: PositionUpdateRequest = {
      userId: 'user123',
      position: {
        mapId: 'plaza',
        x: 150,
        y: 250,
        timestamp: Date.now(),
      },
    };

    it('应该成功更新用户位置', async () => {
      const mockBroadcastTargets = ['user456', 'user789'];

      mockLocationBroadcastCore.setUserPosition.mockResolvedValue(undefined);
      mockLocationBroadcastCore.getMapPositions.mockResolvedValue([
        { userId: 'user456', mapId: 'plaza' },
        { userId: 'user789', mapId: 'plaza' },
        { userId: 'user123', mapId: 'plaza' }, // 当前用户，应该被过滤掉
      ]);

      const result = await service.updatePosition(mockUpdateRequest);

      expect(result.success).toBe(true);
      expect(result.position).toMatchObject({
        userId: mockUpdateRequest.userId,
        x: mockUpdateRequest.position.x,
        y: mockUpdateRequest.position.y,
        mapId: mockUpdateRequest.position.mapId,
      });
      expect(result.broadcastTargets).toEqual(mockBroadcastTargets);
      expect(result.message).toBe('位置更新成功');
    });

    it('应该验证位置数据格式', async () => {
      const invalidRequest = {
        ...mockUpdateRequest,
        position: { ...mockUpdateRequest.position, x: 'invalid' as any },
      };

      await expect(service.updatePosition(invalidRequest)).rejects.toThrow(BadRequestException);
    });

    it('应该验证坐标范围', async () => {
      const invalidRequest = {
        ...mockUpdateRequest,
        position: { ...mockUpdateRequest.position, x: 9999999 },
      };

      await expect(service.updatePosition(invalidRequest)).rejects.toThrow(BadRequestException);
    });
  });

  describe('getSessionStats', () => {
    it('应该返回会话统计信息', async () => {
      const mockUsers: SessionUser[] = [
        {
          userId: 'user1',
          socketId: 'socket1',
          joinedAt: Date.now(),
          lastSeen: Date.now(),
          status: SessionUserStatus.ONLINE,
          metadata: {},
        },
        {
          userId: 'user2',
          socketId: 'socket2',
          joinedAt: Date.now(),
          lastSeen: Date.now(),
          status: SessionUserStatus.ONLINE,
          metadata: {},
        },
      ];

      const mockPositions: Position[] = [
        { userId: 'user1', x: 100, y: 200, mapId: 'plaza', timestamp: Date.now(), metadata: {} },
        { userId: 'user2', x: 150, y: 250, mapId: 'forest', timestamp: Date.now(), metadata: {} },
      ];

      mockLocationBroadcastCore.getSessionUsers.mockResolvedValue(mockUsers);
      mockLocationBroadcastCore.getSessionPositions.mockResolvedValue(mockPositions);

      const result = await service.getSessionStats('session123');

      expect(result.sessionId).toBe('session123');
      expect(result.onlineUsers).toBe(2);
      expect(result.totalUsers).toBe(2);
      expect(result.activeMaps).toEqual(['plaza', 'forest']);
    });

    it('应该在会话不存在时抛出异常', async () => {
      mockLocationBroadcastCore.getSessionUsers.mockRejectedValue(new Error('会话不存在'));

      await expect(service.getSessionStats('invalid_session')).rejects.toThrow(NotFoundException);
    });
  });

  describe('getMapPositions', () => {
    it('应该返回地图中的所有用户位置', async () => {
      const mockPositions: Position[] = [
        { userId: 'user1', x: 100, y: 200, mapId: 'plaza', timestamp: Date.now(), metadata: {} },
        { userId: 'user2', x: 150, y: 250, mapId: 'plaza', timestamp: Date.now(), metadata: {} },
      ];

      mockLocationBroadcastCore.getMapPositions.mockResolvedValue(mockPositions);

      const result = await service.getMapPositions('plaza');

      expect(result).toEqual(mockPositions);
      expect(mockLocationBroadcastCore.getMapPositions).toHaveBeenCalledWith('plaza');
    });

    it('应该在获取失败时返回空数组', async () => {
      mockLocationBroadcastCore.getMapPositions.mockRejectedValue(new Error('获取失败'));

      const result = await service.getMapPositions('plaza');

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

  describe('cleanupUserData', () => {
    it('应该成功清理用户数据', async () => {
      mockLocationBroadcastCore.cleanupUserData.mockResolvedValue(undefined);

      const result = await service.cleanupUserData('user123');

      expect(result).toBe(true);
      expect(mockLocationBroadcastCore.cleanupUserData).toHaveBeenCalledWith('user123');
    });

    it('应该在清理失败时返回false', async () => {
      mockLocationBroadcastCore.cleanupUserData.mockRejectedValue(new Error('清理失败'));

      const result = await service.cleanupUserData('user123');

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

  describe('私有方法测试', () => {
    describe('validateJoinSessionRequest', () => {
      it('应该验证必填字段', async () => {
        const invalidRequests = [
          { userId: '', sessionId: 'session123', socketId: 'socket123' },
          { userId: 'user123', sessionId: '', socketId: 'socket123' },
          { userId: 'user123', sessionId: 'session123', socketId: '' },
        ];

        for (const request of invalidRequests) {
          await expect(service.joinSession(request as any)).rejects.toThrow(BadRequestException);
        }
      });

      it('应该验证会话ID长度', async () => {
        const longSessionId = 'a'.repeat(101);
        const request = {
          userId: 'user123',
          sessionId: longSessionId,
          socketId: 'socket123',
        };

        await expect(service.joinSession(request)).rejects.toThrow(BadRequestException);
      });
    });

    describe('validatePositionData', () => {
      it('应该验证位置数据的完整性', async () => {
        const invalidPositions = [
          { mapId: '', x: 100, y: 200 },
          { mapId: 'plaza', x: NaN, y: 200 },
          { mapId: 'plaza', x: 100, y: Infinity },
        ];

        for (const position of invalidPositions) {
          const request = { userId: 'user123', position };
          await expect(service.updatePosition(request)).rejects.toThrow(BadRequestException);
        }
      });
    });
  });
});