/**
 * Zulip WebSocket网关测试
 * 
 * 功能描述：
 * - 测试ZulipWebSocketGateway的核心功能
 * - 包含属性测试验证WebSocket连接和会话管理
 * 
 * **Feature: zulip-integration, Property 1: WebSocket连接和会话管理**
 * **Validates: Requirements 1.1, 1.2, 1.3, 1.4**
 * 
 * @author angjustinl
 * @version 1.0.0
 * @since 2025-12-25
 */

import { Test, TestingModule } from '@nestjs/testing';
import { Logger } from '@nestjs/common';
import * as fc from 'fast-check';
import { ZulipWebSocketGateway } from './zulip_websocket.gateway';
import { ZulipService, LoginResponse, ChatMessageResponse } from './zulip.service';
import { SessionManagerService, GameSession } from './services/session_manager.service';
import { WebSocketServer, WebSocket } from 'ws';

describe('ZulipWebSocketGateway', () => {
  let gateway: ZulipWebSocketGateway;
  let mockZulipService: jest.Mocked<ZulipService>;
  let mockSessionManager: jest.Mocked<SessionManagerService>;
  let mockServer: jest.Mocked<WebSocketServer>;

  // 跟踪会话状态
  let sessionStore: Map<string, {
    authenticated: boolean;
    userId: string | null;
    sessionId: string | null;
    username: string | null;
    currentMap: string;
  }>;

  // 创建模拟ExtendedWebSocket
  const createMockSocket = (id: string): jest.Mocked<WebSocket> & { id: string; data?: any } => {
    const data: any = {
      authenticated: false,
      userId: null,
      sessionId: null,
      username: null,
      connectedAt: new Date(),
    };

    return {
      id,
      data,
      send: jest.fn(),
      close: jest.fn(),
      terminate: jest.fn(),
      ping: jest.fn(),
      pong: jest.fn(),
      readyState: WebSocket.OPEN,
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
      dispatchEvent: jest.fn(),
    } as any;
  };

  beforeEach(async () => {
    jest.clearAllMocks();
    sessionStore = new Map();

    // Mock NestJS Logger
    jest.spyOn(Logger.prototype, 'log').mockImplementation();
    jest.spyOn(Logger.prototype, 'error').mockImplementation();
    jest.spyOn(Logger.prototype, 'warn').mockImplementation();
    jest.spyOn(Logger.prototype, 'debug').mockImplementation();

    mockZulipService = {
      handlePlayerLogin: jest.fn(),
      handlePlayerLogout: jest.fn(),
      sendChatMessage: jest.fn(),
      updatePlayerPosition: jest.fn(),
      getSession: jest.fn(),
      getSocketsInMap: jest.fn(),
    } as any;

    mockSessionManager = {
      createSession: jest.fn(),
      getSession: jest.fn(),
      destroySession: jest.fn(),
      updatePlayerPosition: jest.fn(),
      getSocketsInMap: jest.fn(),
      injectContext: jest.fn(),
    } as any;

    mockServer = {
      to: jest.fn().mockReturnThis(),
      emit: jest.fn(),
      fetchSockets: jest.fn().mockResolvedValue([]),
    } as any;

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ZulipWebSocketGateway,
        {
          provide: ZulipService,
          useValue: mockZulipService,
        },
        {
          provide: SessionManagerService,
          useValue: mockSessionManager,
        },
      ],
    }).compile();

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

  afterEach(() => {
    sessionStore.clear();
  });

  it('should be defined', () => {
    expect(gateway).toBeDefined();
  });

  describe('handleConnection - 处理连接建立', () => {
    it('应该成功处理新连接', async () => {
      const mockSocket = createMockSocket('socket-123');

      await gateway.handleConnection(mockSocket);

      expect(mockSocket.data.authenticated).toBe(false);
      expect(mockSocket.data.userId).toBeNull();
      expect(mockSocket.data.sessionId).toBeNull();
      expect(mockSocket.data.connectedAt).toBeInstanceOf(Date);
      expect(Logger.prototype.log).toHaveBeenCalledWith(
        '新的WebSocket连接建立',
        expect.objectContaining({
          socketId: 'socket-123',
        })
      );
    });
  });

  describe('handleDisconnect - 处理连接断开', () => {
    it('应该成功处理未认证用户的断开', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = false;

      await gateway.handleDisconnect(mockSocket);

      expect(mockZulipService.handlePlayerLogout).not.toHaveBeenCalled();
      expect(Logger.prototype.log).toHaveBeenCalledWith(
        'WebSocket连接断开',
        expect.objectContaining({
          socketId: 'socket-123',
          authenticated: false,
        })
      );
    });

    it('应该成功处理已认证用户的断开并清理资源', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      mockSocket.data.userId = 'user-456';

      mockZulipService.handlePlayerLogout.mockResolvedValue(undefined);

      await gateway.handleDisconnect(mockSocket);

      expect(mockZulipService.handlePlayerLogout).toHaveBeenCalledWith('socket-123');
      expect(Logger.prototype.log).toHaveBeenCalledWith(
        'WebSocket连接断开',
        expect.objectContaining({
          socketId: 'socket-123',
          authenticated: true,
          userId: 'user-456',
        })
      );
    });

    it('应该在登出处理失败时记录错误但不抛出异常', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      mockSocket.data.userId = 'user-456';

      mockZulipService.handlePlayerLogout.mockRejectedValue(new Error('Logout failed'));

      // 不应该抛出异常
      await expect(gateway.handleDisconnect(mockSocket)).resolves.not.toThrow();

      expect(Logger.prototype.error).toHaveBeenCalled();
    });
  });

  describe('handleLogin - 处理登录', () => {
    it('应该成功处理有效的登录请求', async () => {
      const mockSocket = createMockSocket('socket-123');
      const loginData = { type: 'login' as const, token: 'valid-token' };

      const loginResponse: LoginResponse = {
        success: true,
        sessionId: 'session-789',
        userId: 'user-456',
        username: 'TestUser',
        currentMap: 'novice_village',
      };
      mockZulipService.handlePlayerLogin.mockResolvedValue(loginResponse);

      await gateway.handleLogin(mockSocket, loginData);

      expect(mockZulipService.handlePlayerLogin).toHaveBeenCalledWith({
        token: 'valid-token',
        socketId: 'socket-123',
      });
      expect(mockSocket.data.authenticated).toBe(true);
      expect(mockSocket.data.sessionId).toBe('session-789');
      expect(mockSocket.data.userId).toBe('user-456');
      expect(mockSocket.emit).toHaveBeenCalledWith('login_success', expect.objectContaining({
        t: 'login_success',
        sessionId: 'session-789',
        userId: 'user-456',
        username: 'TestUser',
        currentMap: 'novice_village',
      }));
    });

    it('应该拒绝无效的登录请求格式', async () => {
      const mockSocket = createMockSocket('socket-123');
      const invalidData = { type: 'invalid' } as any;

      await gateway.handleLogin(mockSocket, invalidData);

      expect(mockZulipService.handlePlayerLogin).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('login_error', expect.objectContaining({
        t: 'login_error',
        message: '登录请求格式无效',
      }));
    });

    it('应该拒绝空Token的登录请求', async () => {
      const mockSocket = createMockSocket('socket-123');
      const loginData = { type: 'login' as const, token: '' };

      await gateway.handleLogin(mockSocket, loginData);

      expect(mockZulipService.handlePlayerLogin).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('login_error', expect.objectContaining({
        t: 'login_error',
      }));
    });

    it('应该拒绝已登录用户的重复登录', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      mockSocket.data.userId = 'user-456';
      const loginData = { type: 'login' as const, token: 'another-token' };

      await gateway.handleLogin(mockSocket, loginData);

      expect(mockZulipService.handlePlayerLogin).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('login_error', expect.objectContaining({
        t: 'login_error',
        message: '您已经登录',
      }));
    });

    it('应该处理登录失败的情况', async () => {
      const mockSocket = createMockSocket('socket-123');
      const loginData = { type: 'login' as const, token: 'invalid-token' };

      const loginResponse: LoginResponse = {
        success: false,
        error: 'Token验证失败',
      };
      mockZulipService.handlePlayerLogin.mockResolvedValue(loginResponse);

      await gateway.handleLogin(mockSocket, loginData);

      expect(mockSocket.data.authenticated).toBe(false);
      expect(mockSocket.emit).toHaveBeenCalledWith('login_error', expect.objectContaining({
        t: 'login_error',
        message: 'Token验证失败',
      }));
    });
  });

  describe('handleChat - 处理聊天消息', () => {
    it('应该成功处理已认证用户的聊天消息', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      mockSocket.data.userId = 'user-456';
      const chatData = { t: 'chat' as const, content: 'Hello', scope: 'local' };

      const chatResponse: ChatMessageResponse = {
        success: true,
        messageId: 'msg-123',
      };
      mockZulipService.sendChatMessage.mockResolvedValue(chatResponse);

      await gateway.handleChat(mockSocket, chatData);

      expect(mockZulipService.sendChatMessage).toHaveBeenCalledWith({
        socketId: 'socket-123',
        content: 'Hello',
        scope: 'local',
      });
      expect(mockSocket.emit).toHaveBeenCalledWith('chat_sent', expect.objectContaining({
        t: 'chat_sent',
        messageId: 'msg-123',
      }));
    });

    it('应该拒绝未认证用户的聊天消息', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = false;
      const chatData = { t: 'chat' as const, content: 'Hello', scope: 'local' };

      await gateway.handleChat(mockSocket, chatData);

      expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('chat_error', expect.objectContaining({
        t: 'chat_error',
        message: '请先登录',
      }));
    });

    it('应该拒绝无效格式的聊天消息', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      const invalidData = { t: 'invalid' } as any;

      await gateway.handleChat(mockSocket, invalidData);

      expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('chat_error', expect.objectContaining({
        t: 'chat_error',
        message: '消息格式无效',
      }));
    });

    it('应该拒绝空内容的聊天消息', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      const chatData = { t: 'chat' as const, content: '   ', scope: 'local' };

      await gateway.handleChat(mockSocket, chatData);

      expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
      expect(mockSocket.emit).toHaveBeenCalledWith('chat_error', expect.objectContaining({
        t: 'chat_error',
        message: '消息内容不能为空',
      }));
    });
  });

  describe('handlePositionUpdate - 处理位置更新', () => {
    it('应该成功处理已认证用户的位置更新', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      const positionData = { t: 'position' as const, x: 100, y: 200, mapId: 'tavern' };

      mockZulipService.updatePlayerPosition.mockResolvedValue(true);

      await gateway.handlePositionUpdate(mockSocket, positionData);

      expect(mockZulipService.updatePlayerPosition).toHaveBeenCalledWith({
        socketId: 'socket-123',
        x: 100,
        y: 200,
        mapId: 'tavern',
      });
    });

    it('应该忽略未认证用户的位置更新', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = false;
      const positionData = { t: 'position' as const, x: 100, y: 200, mapId: 'tavern' };

      await gateway.handlePositionUpdate(mockSocket, positionData);

      expect(mockZulipService.updatePlayerPosition).not.toHaveBeenCalled();
    });

    it('应该忽略无效格式的位置更新', async () => {
      const mockSocket = createMockSocket('socket-123');
      mockSocket.data.authenticated = true;
      const invalidData = { t: 'position' as const, x: 'invalid', y: 200, mapId: 'tavern' } as any;

      await gateway.handlePositionUpdate(mockSocket, invalidData);

      expect(mockZulipService.updatePlayerPosition).not.toHaveBeenCalled();
    });
  });

  describe('sendChatRender - 发送聊天渲染消息', () => {
    it('应该成功发送聊天渲染消息', () => {
      gateway.sendChatRender('socket-123', 'TestUser', 'Hello', true);

      expect(mockServer.to).toHaveBeenCalledWith('socket-123');
      expect(mockServer.emit).toHaveBeenCalledWith('chat_render', {
        t: 'chat_render',
        from: 'TestUser',
        txt: 'Hello',
        bubble: true,
      });
    });
  });

  describe('broadcastToMap - 向地图广播消息', () => {
    it('应该成功向地图中的所有玩家广播消息', async () => {
      mockSessionManager.getSocketsInMap.mockResolvedValue(['socket-1', 'socket-2', 'socket-3']);

      await gateway.broadcastToMap('tavern', 'test_event', { data: 'test' });

      expect(mockSessionManager.getSocketsInMap).toHaveBeenCalledWith('tavern');
      expect(mockServer.to).toHaveBeenCalledTimes(3);
      expect(mockServer.emit).toHaveBeenCalledTimes(3);
    });

    it('应该在地图为空时不发送任何消息', async () => {
      mockSessionManager.getSocketsInMap.mockResolvedValue([]);

      await gateway.broadcastToMap('empty_map', 'test_event', { data: 'test' });

      expect(mockServer.to).not.toHaveBeenCalled();
    });
  });


  /**
   * 属性测试: WebSocket连接和会话管理
   * 
   * **Feature: zulip-integration, Property 1: WebSocket连接和会话管理**
   * **Validates: Requirements 1.1, 1.2, 1.3, 1.4**
   * 
   * 对于任何有效的游戏客户端登录请求，系统应该验证Token，建立WebSocket连接，
   * 分配唯一会话ID，并在连接断开或超时时正确清理所有相关资源
   */
  describe('Property 1: WebSocket连接和会话管理', () => {
    /**
     * 属性: 对于任何有效的Token，登录后应该分配唯一的会话ID
     * 验证需求 1.1: 游戏客户端发送登录包时系统应验证游戏Token并建立WebSocket连接
     * 验证需求 1.2: WebSocket连接建立成功时系统应为该连接分配唯一的会话ID
     */
    it('对于任何有效的Token，登录后应该分配唯一的会话ID', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的token（不以invalid开头）
          fc.string({ minLength: 5, maxLength: 100 }).filter(s => 
            s.trim().length > 0 && !s.toLowerCase().startsWith('invalid')
          ),
          // 生成有效的userId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的username
          fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0),
          async (socketId, token, userId, username) => {
            // 创建模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            
            // 生成唯一的sessionId
            const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
            
            // 模拟登录成功响应
            const loginResponse: LoginResponse = {
              success: true,
              sessionId,
              userId: userId.trim(),
              username: username.trim(),
              currentMap: 'novice_village',
            };
            mockZulipService.handlePlayerLogin.mockResolvedValue(loginResponse);

            // 执行登录
            await gateway.handleLogin(mockSocket, {
              type: 'login',
              token: token.trim(),
            });

            // 验证会话ID被分配
            expect(mockSocket.data.sessionId).toBe(sessionId);
            expect(mockSocket.data.authenticated).toBe(true);
            expect(mockSocket.data.userId).toBe(userId.trim());
            
            // 验证登录成功消息被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'login_success',
              expect.objectContaining({
                t: 'login_success',
                sessionId,
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何无效的Token，登录应该失败并返回错误
     * 验证需求 1.1: 游戏客户端发送登录包时系统应验证游戏Token
     */
    it('对于任何无效的Token，登录应该失败并返回错误', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成无效的token（以invalid开头）
          fc.string({ minLength: 1, maxLength: 50 }).map(s => `invalid_${s}`),
          async (socketId, token) => {
            // 创建模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            
            // 模拟登录失败响应
            const loginResponse: LoginResponse = {
              success: false,
              error: 'Token验证失败',
            };
            mockZulipService.handlePlayerLogin.mockResolvedValue(loginResponse);

            // 执行登录
            await gateway.handleLogin(mockSocket, {
              type: 'login',
              token: token.trim(),
            });

            // 验证登录失败
            expect(mockSocket.data.authenticated).toBe(false);
            expect(mockSocket.data.sessionId).toBeNull();
            
            // 验证错误消息被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'login_error',
              expect.objectContaining({
                t: 'login_error',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何已认证的连接断开，系统应该清理所有相关资源
     * 验证需求 1.3: 客户端断开连接时系统应清理相关的会话数据和Zulip队列
     */
    it('对于任何已认证的连接断开，系统应该清理所有相关资源', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的userId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的sessionId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          async (socketId, userId, sessionId) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = userId.trim();
            mockSocket.data.sessionId = sessionId.trim();
            
            // 模拟登出成功
            mockZulipService.handlePlayerLogout.mockResolvedValue(undefined);

            // 执行断开连接
            await gateway.handleDisconnect(mockSocket);

            // 验证登出处理被调用
            expect(mockZulipService.handlePlayerLogout).toHaveBeenCalledWith(socketId.trim());
            
            // 验证日志记录
            expect(Logger.prototype.log).toHaveBeenCalledWith(
              'WebSocket连接断开',
              expect.objectContaining({
                socketId: socketId.trim(),
                authenticated: true,
                userId: userId.trim(),
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何未认证的连接断开，系统不应该尝试清理会话资源
     * 验证需求 1.3: 客户端断开连接时系统应清理相关的会话数据
     */
    it('对于任何未认证的连接断开，系统不应该尝试清理会话资源', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          async (socketId) => {
            // 创建未认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = false;

            // 执行断开连接
            await gateway.handleDisconnect(mockSocket);

            // 验证登出处理未被调用
            expect(mockZulipService.handlePlayerLogout).not.toHaveBeenCalled();
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 连接-登录-断开的完整生命周期应该正确管理状态
     * 验证需求 1.1, 1.2, 1.3: 完整的连接生命周期管理
     */
    it('连接-登录-断开的完整生命周期应该正确管理状态', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的token
          fc.string({ minLength: 5, maxLength: 100 }).filter(s => 
            s.trim().length > 0 && !s.toLowerCase().startsWith('invalid')
          ),
          // 生成有效的userId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          async (socketId, token, userId) => {
            // 1. 创建连接
            const mockSocket = createMockSocket(socketId.trim());
            await gateway.handleConnection(mockSocket);
            
            // 验证初始状态
            expect(mockSocket.data.authenticated).toBe(false);
            expect(mockSocket.data.sessionId).toBeNull();

            // 2. 执行登录
            const sessionId = `session_${Date.now()}`;
            const loginResponse: LoginResponse = {
              success: true,
              sessionId,
              userId: userId.trim(),
              username: 'TestUser',
              currentMap: 'novice_village',
            };
            mockZulipService.handlePlayerLogin.mockResolvedValue(loginResponse);

            await gateway.handleLogin(mockSocket, {
              type: 'login',
              token: token.trim(),
            });

            // 验证登录后状态
            expect(mockSocket.data.authenticated).toBe(true);
            expect(mockSocket.data.sessionId).toBe(sessionId);
            expect(mockSocket.data.userId).toBe(userId.trim());

            // 3. 断开连接
            mockZulipService.handlePlayerLogout.mockResolvedValue(undefined);
            await gateway.handleDisconnect(mockSocket);

            // 验证清理被调用
            expect(mockZulipService.handlePlayerLogout).toHaveBeenCalledWith(socketId.trim());
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何登录请求，空Token应该被拒绝
     * 验证需求 1.1: 系统应验证游戏Token
     */
    it('对于任何登录请求，空Token应该被拒绝', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成空或只有空白的token
          fc.constantFrom('', '   ', '\t', '\n'),
          async (socketId, emptyToken) => {
            // 创建模拟Socket
            const mockSocket = createMockSocket(socketId.trim());

            // 执行登录
            await gateway.handleLogin(mockSocket, {
              type: 'login',
              token: emptyToken,
            });

            // 验证登录被拒绝
            expect(mockSocket.data.authenticated).toBe(false);
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'login_error',
              expect.objectContaining({
                t: 'login_error',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);
  });

  /**
   * 属性测试: 消息路由和权限验证
   * 
   * **Feature: zulip-integration, Property 3: 消息路由和权限验证**
   * **Validates: Requirements 4.1, 4.2, 7.2, 7.4**
   * 
   * 对于任何游戏聊天消息，系统应该根据玩家当前位置正确确定目标Stream/Topic，
   * 验证发送权限，并使用正确的API Key发送到Zulip
   */
  describe('Property 3: 消息路由和权限验证', () => {
    /**
     * 属性: 对于任何已认证用户的有效聊天消息，系统应该正确路由到ZulipService
     * 验证需求 4.1: 玩家在游戏中发送聊天消息时系统应根据玩家当前位置确定目标Stream和Topic
     */
    it('对于任何已认证用户的有效聊天消息，系统应该正确路由到ZulipService', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的userId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的消息内容（非空）
          fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
          // 生成有效的scope
          fc.constantFrom('local', 'global', 'tavern', 'notice_board'),
          async (socketId, userId, content, scope) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = userId.trim();
            
            // 模拟消息发送成功
            const chatResponse: ChatMessageResponse = {
              success: true,
              messageId: `msg_${Date.now()}`,
            };
            mockZulipService.sendChatMessage.mockResolvedValue(chatResponse);

            // 执行聊天消息发送
            await gateway.handleChat(mockSocket, {
              t: 'chat',
              content: content.trim(),
              scope,
            });

            // 验证ZulipService被正确调用
            expect(mockZulipService.sendChatMessage).toHaveBeenCalledWith({
              socketId: socketId.trim(),
              content: content.trim(),
              scope,
            });
            
            // 验证成功响应被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'chat_sent',
              expect.objectContaining({
                t: 'chat_sent',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何未认证用户的聊天消息，系统应该拒绝并返回错误
     * 验证需求 7.2: 玩家尝试发送消息时系统应验证玩家是否有权限访问目标Stream
     */
    it('对于任何未认证用户的聊天消息，系统应该拒绝并返回错误', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的消息内容
          fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
          // 生成有效的scope
          fc.constantFrom('local', 'global', 'tavern'),
          async (socketId, content, scope) => {
            // 创建未认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = false;

            // 执行聊天消息发送
            await gateway.handleChat(mockSocket, {
              t: 'chat',
              content: content.trim(),
              scope,
            });

            // 验证ZulipService未被调用
            expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
            
            // 验证错误响应被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'chat_error',
              expect.objectContaining({
                t: 'chat_error',
                message: '请先登录',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何空内容的聊天消息，系统应该拒绝发送
     * 验证需求 4.1: 系统应正确处理消息内容
     */
    it('对于任何空内容的聊天消息，系统应该拒绝发送', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成空或只有空白的内容（但不是空字符串，因为空字符串会被格式验证捕获）
          fc.constantFrom('   ', '\t', '\n', '  \t  \n  '),
          // 生成有效的scope
          fc.constantFrom('local', 'global'),
          async (socketId, emptyContent, scope) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = 'user-123';

            // 执行聊天消息发送
            await gateway.handleChat(mockSocket, {
              t: 'chat',
              content: emptyContent,
              scope,
            });

            // 验证ZulipService未被调用
            expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
            
            // 验证错误响应被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'chat_error',
              expect.objectContaining({
                t: 'chat_error',
                message: '消息内容不能为空',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何消息发送失败的情况，系统应该返回错误信息
     * 验证需求 4.2: 系统应使用玩家的Zulip API Key发送消息到对应Stream
     */
    it('对于任何消息发送失败的情况，系统应该返回错误信息', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的消息内容
          fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
          // 生成错误消息
          fc.constantFrom('权限不足', '频率限制', 'Zulip服务不可用', '消息过滤失败'),
          async (socketId, content, errorMessage) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = 'user-123';
            
            // 模拟消息发送失败
            const chatResponse: ChatMessageResponse = {
              success: false,
              error: errorMessage,
            };
            mockZulipService.sendChatMessage.mockResolvedValue(chatResponse);

            // 执行聊天消息发送
            await gateway.handleChat(mockSocket, {
              t: 'chat',
              content: content.trim(),
              scope: 'local',
            });

            // 验证错误响应被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'chat_error',
              expect.objectContaining({
                t: 'chat_error',
                message: errorMessage,
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何无效格式的聊天消息，系统应该拒绝处理
     * 验证需求 4.1: 系统应正确验证消息格式
     */
    it('对于任何无效格式的聊天消息，系统应该拒绝处理', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成无效的消息类型
          fc.constantFrom('invalid', 'message', 'msg', 'text', ''),
          async (socketId, invalidType) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = 'user-123';

            // 执行无效格式的聊天消息
            await gateway.handleChat(mockSocket, {
              t: invalidType as any,
              content: 'Hello',
              scope: 'local',
            });

            // 验证ZulipService未被调用
            expect(mockZulipService.sendChatMessage).not.toHaveBeenCalled();
            
            // 验证错误响应被发送
            expect(mockSocket.emit).toHaveBeenCalledWith(
              'chat_error',
              expect.objectContaining({
                t: 'chat_error',
                message: '消息格式无效',
              })
            );
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何位置更新，已认证用户的更新应该被正确处理
     * 验证需求 7.4: 玩家位置与请求的Stream不匹配时系统应拒绝消息发送请求
     */
    it('对于任何位置更新，已认证用户的更新应该被正确处理', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的坐标
          fc.integer({ min: 0, max: 10000 }),
          fc.integer({ min: 0, max: 10000 }),
          // 生成有效的mapId
          fc.constantFrom('novice_village', 'tavern', 'market', 'forest'),
          async (socketId, x, y, mapId) => {
            // 创建已认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = true;
            mockSocket.data.userId = 'user-123';
            
            // 模拟位置更新成功
            mockZulipService.updatePlayerPosition.mockResolvedValue(true);

            // 执行位置更新
            await gateway.handlePositionUpdate(mockSocket, {
              t: 'position',
              x,
              y,
              mapId,
            });

            // 验证ZulipService被正确调用
            expect(mockZulipService.updatePlayerPosition).toHaveBeenCalledWith({
              socketId: socketId.trim(),
              x,
              y,
              mapId,
            });
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何位置更新，未认证用户的更新应该被忽略
     * 验证需求 7.4: 系统应验证用户权限
     */
    it('对于任何位置更新，未认证用户的更新应该被忽略', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的socketId
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成有效的坐标
          fc.integer({ min: 0, max: 10000 }),
          fc.integer({ min: 0, max: 10000 }),
          // 生成有效的mapId
          fc.constantFrom('novice_village', 'tavern', 'market'),
          async (socketId, x, y, mapId) => {
            // 创建未认证的模拟Socket
            const mockSocket = createMockSocket(socketId.trim());
            mockSocket.data.authenticated = false;

            // 执行位置更新
            await gateway.handlePositionUpdate(mockSocket, {
              t: 'position',
              x,
              y,
              mapId,
            });

            // 验证ZulipService未被调用
            expect(mockZulipService.updatePlayerPosition).not.toHaveBeenCalled();
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);
  });
});
