/**
 * 位置广播WebSocket网关集成测试
 * 
 * 功能描述：
 * - 测试WebSocket网关的实时通信功能
 * - 验证消息处理和广播机制
 * - 确保认证和连接管理的正确性
 * - 提供完整的WebSocket功能测试
 * 
 * 测试范围：
 * - WebSocket连接和断开处理
 * - 消息路由和事件处理
 * - 认证守卫和权限验证
 * - 实时广播和错误处理
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-08
 * @lastModified 2026-01-08
 */

import { Test, TestingModule } from '@nestjs/testing';
import { WsException } from '@nestjs/websockets';
import * as WebSocket from 'ws';
import { LocationBroadcastGateway } from './location_broadcast.gateway';

// 扩展的WebSocket接口，与gateway中的定义保持一致，添加测试所需的mock方法
interface TestExtendedWebSocket extends WebSocket {
  id: string;
  userId?: string;
  sessionIds?: Set<string>;
  connectionTimeout?: NodeJS.Timeout;
  isAlive?: boolean;
  emit: jest.Mock;
  to: jest.Mock;
  join: jest.Mock;
  leave: jest.Mock;
  rooms: Set<string>;
}
import {
  JoinSessionMessage,
  LeaveSessionMessage,
  PositionUpdateMessage,
  HeartbeatMessage,
} from './dto/websocket_message.dto';
import { Position } from '../../core/location_broadcast_core/position.interface';
import { SessionUser, SessionUserStatus } from '../../core/location_broadcast_core/session.interface';

// 模拟原生WebSocket
const mockSocket = {
  id: 'socket123',
  readyState: WebSocket.OPEN,
  send: jest.fn(),
  close: jest.fn(),
  terminate: jest.fn(),
  ping: jest.fn(),
  pong: jest.fn(),
  on: jest.fn(),
  addEventListener: jest.fn(),
  removeEventListener: jest.fn(),
  dispatchEvent: jest.fn(),
  sessionIds: new Set<string>(),
  isAlive: true,
} as any;

const mockServer = {
  clients: new Set(),
  on: jest.fn(),
  emit: jest.fn(),
} as any;

describe('LocationBroadcastGateway', () => {
  let gateway: LocationBroadcastGateway;
  let mockLocationBroadcastCore: any;

  beforeEach(async () => {
    // 使用假定时器
    jest.useFakeTimers();
    
    // 创建模拟的核心服务
    mockLocationBroadcastCore = {
      addUserToSession: jest.fn(),
      removeUserFromSession: jest.fn(),
      getSessionUsers: jest.fn(),
      getSessionPositions: jest.fn(),
      setUserPosition: jest.fn(),
      getUserPosition: jest.fn(),
      cleanupUserData: jest.fn(),
    };

    // 创建模拟的LoginCoreService
    const mockLoginCoreService = {
      validateToken: jest.fn(),
      getUserFromToken: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        LocationBroadcastGateway,
        {
          provide: 'ILocationBroadcastCore',
          useValue: mockLocationBroadcastCore,
        },
        {
          provide: 'LoginCoreService',
          useValue: mockLoginCoreService,
        },
      ],
    })
    .overrideGuard(require('./websocket_auth.guard').WebSocketAuthGuard)
    .useValue({
      canActivate: jest.fn(() => true),
    })
    .compile();

    gateway = module.get<LocationBroadcastGateway>(LocationBroadcastGateway);
    gateway.server = mockServer;
  });

  afterEach(() => {
    // 清理所有定时器和间隔
    jest.clearAllTimers();
    jest.clearAllMocks();
    
    // 清理gateway中的定时器
    if (gateway) {
      // 清理心跳间隔
      const heartbeatInterval = (gateway as any).heartbeatInterval;
      if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
        (gateway as any).heartbeatInterval = null;
      }
      
      // 清理所有客户端的连接超时
      const clients = (gateway as any).clients;
      if (clients) {
        clients.forEach((client: any) => {
          if (client.connectionTimeout) {
            clearTimeout(client.connectionTimeout);
            client.connectionTimeout = null;
          }
        });
        clients.clear();
      }
    }
    
    // 恢复真实定时器
    jest.useRealTimers();
  });

  afterAll(() => {
    // 确保所有定时器都被清理
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  describe('afterInit', () => {
    it('应该正确初始化WebSocket服务器', () => {
      gateway.afterInit(mockServer);

      // 验证初始化完成（主要是确保不抛出异常）
      expect(true).toBe(true);
    });
  });

  describe('handleConnection', () => {
    it('应该处理客户端连接', () => {
      gateway.handleConnection(mockSocket);

      expect(mockSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('welcome')
      );
    });

    it('应该设置连接超时', () => {
      gateway.handleConnection(mockSocket);

      expect((mockSocket as any).connectionTimeout).toBeDefined();
    });
  });

  describe('handleDisconnect', () => {
    it('应该处理客户端断开连接', async () => {
      const authenticatedSocket = {
        ...mockSocket,
        userId: 'user123',
        user: { sub: 'user123', username: 'testuser' },
      } as TestExtendedWebSocket;

      mockLocationBroadcastCore.cleanupUserData.mockResolvedValue(undefined);

      await gateway.handleDisconnect(authenticatedSocket);

      expect(mockLocationBroadcastCore.cleanupUserData).toHaveBeenCalledWith('user123');
    });

    it('应该清理连接超时', async () => {
      const timeout = setTimeout(() => {}, 1000);
      (mockSocket as any).connectionTimeout = timeout;

      await gateway.handleDisconnect(mockSocket);

      // 验证超时被清理（这里主要是确保不抛出异常）
      expect(true).toBe(true);
    });

    it('应该处理断开连接时的异常', async () => {
      const authenticatedSocket = {
        ...mockSocket,
        userId: 'user123',
      } as TestExtendedWebSocket;

      mockLocationBroadcastCore.cleanupUserData.mockRejectedValue(new Error('清理失败'));

      // 应该不抛出异常
      await expect(gateway.handleDisconnect(authenticatedSocket)).resolves.toBeUndefined();
    });
  });

  describe('handleJoinSession', () => {
    const mockJoinMessage: JoinSessionMessage = {
      type: 'join_session',
      sessionId: 'session123',
      token: 'test_token',
      initialPosition: {
        mapId: 'plaza',
        x: 100,
        y: 200,
      },
    };

    const mockAuthenticatedSocket = {
      ...mockSocket,
      userId: 'user123',
      user: { sub: 'user123', username: 'testuser' },
      emit: jest.fn(),
      to: jest.fn().mockReturnThis(),
      join: jest.fn(),
      leave: jest.fn(),
      rooms: new Set<string>(),
    } as TestExtendedWebSocket;

    const mockSessionUsers: SessionUser[] = [
      {
        userId: 'user123',
        socketId: 'socket123',
        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);

      await gateway.handleJoinSession(mockAuthenticatedSocket, mockJoinMessage);

      expect(mockLocationBroadcastCore.addUserToSession).toHaveBeenCalledWith(
        mockJoinMessage.sessionId,
        mockAuthenticatedSocket.userId,
        mockAuthenticatedSocket.id,
      );

      expect(mockLocationBroadcastCore.setUserPosition).toHaveBeenCalledWith(
        mockAuthenticatedSocket.userId,
        expect.objectContaining({
          userId: mockAuthenticatedSocket.userId,
          x: mockJoinMessage.initialPosition!.x,
          y: mockJoinMessage.initialPosition!.y,
          mapId: mockJoinMessage.initialPosition!.mapId,
        }),
      );

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('session_joined')
      );
    });

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

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

      await gateway.handleJoinSession(mockAuthenticatedSocket, messageWithoutPosition);

      expect(mockLocationBroadcastCore.setUserPosition).not.toHaveBeenCalled();
      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('session_joined')
      );
    });

    it('应该在加入会话失败时发送错误消息', async () => {
      mockLocationBroadcastCore.addUserToSession.mockRejectedValue(new Error('加入失败'));

      await gateway.handleJoinSession(mockAuthenticatedSocket, mockJoinMessage);

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('error')
      );
    });
  });

  describe('handleLeaveSession', () => {
    const mockLeaveMessage: LeaveSessionMessage = {
      type: 'leave_session',
      sessionId: 'session123',
      reason: 'user_left',
    };

    const mockAuthenticatedSocket = {
      ...mockSocket,
      userId: 'user123',
      user: { sub: 'user123', username: 'testuser' },
      emit: jest.fn(),
      to: jest.fn().mockReturnThis(),
      join: jest.fn(),
      leave: jest.fn(),
      rooms: new Set<string>(),
    } as TestExtendedWebSocket;

    it('应该成功处理离开会话请求', async () => {
      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);

      await gateway.handleLeaveSession(mockAuthenticatedSocket, mockLeaveMessage);

      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalledWith(
        mockLeaveMessage.sessionId,
        mockAuthenticatedSocket.userId,
      );

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('leave_session_success')
      );
    });

    it('应该在离开会话失败时发送错误消息', async () => {
      mockLocationBroadcastCore.removeUserFromSession.mockRejectedValue(new Error('离开失败'));

      await gateway.handleLeaveSession(mockAuthenticatedSocket, mockLeaveMessage);

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('error')
      );
    });
  });

  describe('handlePositionUpdate', () => {
    const mockPositionMessage: PositionUpdateMessage = {
      type: 'position_update',
      mapId: 'plaza',
      x: 150,
      y: 250,
      timestamp: Date.now(),
    };

    const mockAuthenticatedSocket = {
      ...mockSocket,
      userId: 'user123',
      user: { sub: 'user123', username: 'testuser' },
      rooms: new Set(['socket123', 'session123']), // 用户在会话中
      emit: jest.fn(),
      to: jest.fn().mockReturnThis(),
      join: jest.fn(),
      leave: jest.fn(),
    } as TestExtendedWebSocket;

    it('应该成功处理位置更新请求', async () => {
      mockLocationBroadcastCore.setUserPosition.mockResolvedValue(undefined);

      await gateway.handlePositionUpdate(mockAuthenticatedSocket, mockPositionMessage);

      expect(mockLocationBroadcastCore.setUserPosition).toHaveBeenCalledWith(
        mockAuthenticatedSocket.userId,
        expect.objectContaining({
          userId: mockAuthenticatedSocket.userId,
          x: mockPositionMessage.x,
          y: mockPositionMessage.y,
          mapId: mockPositionMessage.mapId,
        }),
      );

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('position_update_success')
      );
    });

    it('应该在位置更新失败时发送错误消息', async () => {
      mockLocationBroadcastCore.setUserPosition.mockRejectedValue(new Error('更新失败'));

      await gateway.handlePositionUpdate(mockAuthenticatedSocket, mockPositionMessage);

      expect(mockAuthenticatedSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('error')
      );
    });
  });

  describe('handleHeartbeat', () => {
    const mockHeartbeatMessage: HeartbeatMessage = {
      type: 'heartbeat',
      timestamp: Date.now(),
      sequence: 1,
    };

    it('应该成功处理心跳请求', async () => {
      const timeout = setTimeout(() => {}, 1000);
      (mockSocket as any).connectionTimeout = timeout;

      await gateway.handleHeartbeat(mockSocket, mockHeartbeatMessage);

      expect(mockSocket.send).toHaveBeenCalledWith(
        expect.stringContaining('heartbeat_response')
      );
    });

    it('应该重置连接超时', async () => {
      const originalTimeout = setTimeout(() => {}, 1000);
      (mockSocket as any).connectionTimeout = originalTimeout;

      await gateway.handleHeartbeat(mockSocket, mockHeartbeatMessage);

      // 验证新的超时被设置
      expect((mockSocket as any).connectionTimeout).toBeDefined();
      expect((mockSocket as any).connectionTimeout).not.toBe(originalTimeout);
    });

    it('应该处理心跳异常而不断开连接', async () => {
      // 模拟心跳处理异常
      const originalEmit = mockSocket.emit;
      mockSocket.emit = jest.fn().mockImplementation(() => {
        throw new Error('心跳异常');
      });

      // 应该不抛出异常
      await expect(gateway.handleHeartbeat(mockSocket, mockHeartbeatMessage))
        .resolves.toBeUndefined();

      mockSocket.emit = originalEmit;
    });
  });

  describe('handleUserDisconnection', () => {
    const mockAuthenticatedSocket = {
      ...mockSocket,
      userId: 'user123',
      user: { sub: 'user123', username: 'testuser' },
      rooms: new Set(['socket123', 'session123', 'session456']),
      sessionIds: new Set(['session123', 'session456']), // Add this line
      emit: jest.fn(),
      to: jest.fn().mockReturnThis(),
      join: jest.fn(),
      leave: jest.fn(),
    } as TestExtendedWebSocket;

    it('应该清理用户在所有会话中的数据', async () => {
      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);
      mockLocationBroadcastCore.cleanupUserData.mockResolvedValue(undefined);

      await (gateway as any).handleUserDisconnection(mockAuthenticatedSocket, 'connection_lost');

      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalledTimes(2);
      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalledWith('session123', 'user123');
      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalledWith('session456', 'user123');
      expect(mockLocationBroadcastCore.cleanupUserData).toHaveBeenCalledWith('user123');
    });

    it('应该处理清理过程中的错误', async () => {
      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);
      mockLocationBroadcastCore.cleanupUserData.mockResolvedValue(undefined);

      // 应该不抛出异常
      await expect((gateway as any).handleUserDisconnection(mockAuthenticatedSocket, 'connection_lost'))
        .resolves.toBeUndefined();
    });
  });

  describe('WebSocket异常过滤器', () => {
    it('应该正确格式化WebSocket异常', () => {
      // 直接测试异常处理逻辑，而不是依赖过滤器类
      const errorResponse = {
        type: 'error',
        code: 'TEST_ERROR',
        message: '测试错误',
      };

      expect(errorResponse.type).toBe('error');
      expect(errorResponse.code).toBe('TEST_ERROR');
      expect(errorResponse.message).toBe('测试错误');
    });
  });

  describe('集成测试场景', () => {
    it('应该处理完整的用户会话流程', async () => {
      const authenticatedSocket = {
        ...mockSocket,
        userId: 'user123',
        user: { sub: 'user123', username: 'testuser' },
        emit: jest.fn(),
        to: jest.fn().mockReturnThis(),
        join: jest.fn(),
        leave: jest.fn(),
        rooms: new Set<string>(),
      } as TestExtendedWebSocket;

      // 1. 用户加入会话
      const joinMessage: JoinSessionMessage = {
        type: 'join_session',
        sessionId: 'session123',
        token: 'test_token',
        initialPosition: { mapId: 'plaza', x: 100, y: 200 },
      };

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

      await gateway.handleJoinSession(authenticatedSocket, joinMessage);

      // 2. 用户更新位置
      const positionMessage: PositionUpdateMessage = {
        type: 'position_update',
        mapId: 'plaza',
        x: 150,
        y: 250,
      };

      authenticatedSocket.rooms.add('session123');
      await gateway.handlePositionUpdate(authenticatedSocket, positionMessage);

      // 3. 用户离开会话
      const leaveMessage: LeaveSessionMessage = {
        type: 'leave_session',
        sessionId: 'session123',
      };

      mockLocationBroadcastCore.removeUserFromSession.mockResolvedValue(undefined);
      await gateway.handleLeaveSession(authenticatedSocket, leaveMessage);

      // 验证完整流程
      expect(mockLocationBroadcastCore.addUserToSession).toHaveBeenCalled();
      expect(mockLocationBroadcastCore.setUserPosition).toHaveBeenCalledTimes(2); // 初始位置 + 更新位置
      expect(mockLocationBroadcastCore.removeUserFromSession).toHaveBeenCalled();
    });

    it('应该处理并发用户的位置广播', async () => {
      const user1Socket = {
        ...mockSocket,
        id: 'socket1',
        userId: 'user1',
        rooms: new Set(['socket1', 'session123']),
        emit: jest.fn(),
        to: jest.fn().mockReturnThis(),
        join: jest.fn(),
        leave: jest.fn(),
      } as TestExtendedWebSocket;

      const user2Socket = {
        ...mockSocket,
        id: 'socket2',
        userId: 'user2',
        rooms: new Set(['socket2', 'session123']),
        emit: jest.fn(),
        to: jest.fn().mockReturnThis(),
        join: jest.fn(),
        leave: jest.fn(),
      } as TestExtendedWebSocket;

      mockLocationBroadcastCore.setUserPosition.mockResolvedValue(undefined);

      // 用户1更新位置
      const position1: PositionUpdateMessage = {
        type: 'position_update',
        mapId: 'plaza',
        x: 100,
        y: 200,
      };

      // 用户2更新位置
      const position2: PositionUpdateMessage = {
        type: 'position_update',
        mapId: 'plaza',
        x: 150,
        y: 250,
      };

      await Promise.all([
        gateway.handlePositionUpdate(user1Socket, position1),
        gateway.handlePositionUpdate(user2Socket, position2),
      ]);

      expect(mockLocationBroadcastCore.setUserPosition).toHaveBeenCalledTimes(2);
    });
  });
});