/**
 * Zulip集成系统端到端测试
 * 
 * 功能描述：
 * - 测试完整的登录到聊天流程
 * - 测试多用户并发聊天场景
 * - 测试错误场景和降级处理
 * 
 * **验证需求: 所有需求**
 * 
 * @author angjustinl
 * @version 1.0.0
 * @since 2025-12-25
 */

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import WebSocket from 'ws';
import { AppModule } from '../../app.module';

// 如果没有设置 RUN_E2E_TESTS 环境变量，跳过这些测试
const describeE2E = process.env.RUN_E2E_TESTS === 'true' ? describe : describe.skip;

describeE2E('Zulip Integration E2E Tests', () => {
  let app: INestApplication;
  let serverUrl: string;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
    await app.listen(0); // 使用随机端口
    
    const address = app.getHttpServer().address();
    const port = address.port;
    serverUrl = `http://localhost:${port}`;
  }, 30000);

  afterAll(async () => {
    if (app) {
      await app.close();
    }
  });

  /**
   * 创建WebSocket客户端连接
   */
  const createClient = (): Promise<ClientSocket> => {
    return new Promise((resolve, reject) => {
      const client = io(`${serverUrl}/game`, {
        transports: ['websocket'],
        autoConnect: true,
      });

      client.on('connect', () => resolve(client));
      client.on('connect_error', (err: any) => reject(err));
      
      setTimeout(() => reject(new Error('Connection timeout')), 5000);
    });
  };

  /**
   * 等待指定事件
   */
  const waitForEvent = <T>(client: ClientSocket, event: string, timeout = 5000): Promise<T> => {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`Timeout waiting for event: ${event}`));
      }, timeout);

      client.once(event, (data: T) => {
        clearTimeout(timer);
        resolve(data);
      });
    });
  };

  /**
   * 测试套件1: 完整的登录到聊天流程测试
   * 验证需求: 1.1, 1.2, 1.3, 2.1, 4.1, 4.2, 5.1
   */
  describe('完整的登录到聊天流程测试', () => {
    let client: ClientSocket;

    afterEach(async () => {
      if (client?.connected) {
        client.disconnect();
      }
    });

    /**
     * 测试: WebSocket连接建立
     * 验证需求 1.1: 游戏客户端发送登录包时系统应验证游戏Token并建立WebSocket连接
     */
    it('应该成功建立WebSocket连接', async () => {
      client = await createClient();
      expect(client.connected).toBe(true);
    });

    /**
     * 测试: 有效Token登录成功
     * 验证需求 1.1, 1.2: 验证Token并分配唯一会话ID
     */
    it('应该使用有效Token成功登录', async () => {
      client = await createClient();
      
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_test_token_123' });
      
      const response = await loginPromise;
      
      expect(response.t).toBe('login_success');
      expect(response.sessionId).toBeDefined();
      expect(response.userId).toBeDefined();
      expect(response.currentMap).toBeDefined();
    });

    /**
     * 测试: 无效Token登录失败
     * 验证需求 1.1: 系统应验证游戏Token
     */
    it('应该拒绝无效Token的登录请求', async () => {
      client = await createClient();
      
      const errorPromise = waitForEvent<any>(client, 'login_error');
      client.emit('login', { type: 'login', token: 'invalid_token' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('login_error');
      expect(response.message).toBeDefined();
    });

    /**
     * 测试: 登录后发送聊天消息
     * 验证需求 4.1, 4.2: 玩家发送聊天消息时系统应根据位置确定目标Stream
     */
    it('应该在登录后成功发送聊天消息', async () => {
      client = await createClient();
      
      // 先登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_test_token_456' });
      await loginPromise;
      
      // 发送聊天消息
      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { t: 'chat', content: 'Hello World!', scope: 'local' });
      
      const response = await chatPromise;
      
      expect(response.t).toBe('chat_sent');
    });

    /**
     * 测试: 未登录时发送消息被拒绝
     * 验证需求 7.2: 系统应验证玩家是否有权限
     */
    it('应该拒绝未登录用户的聊天消息', async () => {
      client = await createClient();
      
      const errorPromise = waitForEvent<any>(client, 'chat_error');
      client.emit('chat', { t: 'chat', content: 'Hello', scope: 'local' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('chat_error');
      expect(response.message).toBe('请先登录');
    });

    /**
     * 测试: 空消息内容被拒绝
     * 验证需求 4.3: 系统应过滤消息内容
     */
    it('应该拒绝空内容的聊天消息', async () => {
      client = await createClient();
      
      // 先登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_test_token_789' });
      await loginPromise;
      
      // 发送空消息
      const errorPromise = waitForEvent<any>(client, 'chat_error');
      client.emit('chat', { t: 'chat', content: '   ', scope: 'local' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('chat_error');
      expect(response.message).toBe('消息内容不能为空');
    });

    /**
     * 测试: 位置更新
     * 验证需求 6.2: 玩家切换地图时系统应更新位置信息
     */
    it('应该成功更新玩家位置', async () => {
      client = await createClient();
      
      // 先登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_test_token_position' });
      await loginPromise;
      
      // 更新位置 - 位置更新不返回确认消息，只需确保不报错
      client.emit('position_update', { t: 'position', x: 100, y: 200, mapId: 'tavern' });
      
      // 等待一小段时间确保消息被处理
      await new Promise(resolve => setTimeout(resolve, 100));
      
      // 如果没有错误，测试通过
      expect(client.connected).toBe(true);
    });
  });

  /**
   * 测试套件2: 多用户并发聊天测试
   * 验证需求: 5.2, 5.5, 6.1, 6.3
   */
  describe('多用户并发聊天测试', () => {
    const clients: ClientSocket[] = [];

    afterEach(async () => {
      // 断开所有客户端
      for (const client of clients) {
        if (client?.connected) {
          client.disconnect();
        }
      }
      clients.length = 0;
    });

    /**
     * 测试: 多用户同时连接
     * 验证需求 6.1: 系统应在Redis中存储会话映射关系
     */
    it('应该支持多用户同时连接', async () => {
      const userCount = 5;
      
      for (let i = 0; i < userCount; i++) {
        const client = await createClient();
        clients.push(client);
        
        const loginPromise = waitForEvent<any>(client, 'login_success');
        client.emit('login', { type: 'login', token: `valid_multi_user_${i}` });
        await loginPromise;
      }
      
      // 验证所有客户端都已连接并登录
      expect(clients.length).toBe(userCount);
      for (const client of clients) {
        expect(client.connected).toBe(true);
      }
    });

    /**
     * 测试: 多用户并发发送消息
     * 验证需求 4.1, 4.2: 多用户同时发送消息
     */
    it('应该正确处理多用户并发发送消息', async () => {
      const userCount = 3;
      
      // 创建并登录多个用户（使用完全不同的token前缀避免userId冲突）
      // userId是从token前8个字符生成的，所以每个用户需要不同的前缀
      const userPrefixes = ['userAAA1', 'userBBB2', 'userCCC3'];
      
      for (let i = 0; i < userCount; i++) {
        const client = await createClient();
        clients.push(client);
        
        const loginPromise = waitForEvent<any>(client, 'login_success');
        // 使用不同的前缀确保每个用户有唯一的userId
        client.emit('login', { type: 'login', token: `${userPrefixes[i]}_concurrent_${Date.now()}` });
        await loginPromise;
        
        // 添加小延迟确保会话完全建立
        await new Promise(resolve => setTimeout(resolve, 50));
      }
      
      // 顺序发送消息（避免并发会话问题）
      for (let i = 0; i < clients.length; i++) {
        const client = clients[i];
        const chatPromise = waitForEvent<any>(client, 'chat_sent');
        client.emit('chat', { 
          t: 'chat', 
          content: `Message from user ${i}`, 
          scope: 'local' 
        });
        const result = await chatPromise;
        expect(result.t).toBe('chat_sent');
      }
    });

    /**
     * 测试: 用户断开连接后资源清理
     * 验证需求 1.3: 客户端断开连接时系统应清理相关资源
     */
    it('应该在用户断开连接后正确清理资源', async () => {
      const client = await createClient();
      clients.push(client);
      
      // 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_cleanup_test' });
      await loginPromise;
      
      // 断开连接
      client.disconnect();
      
      // 等待清理完成
      await new Promise(resolve => setTimeout(resolve, 200));
      
      expect(client.connected).toBe(false);
    });
  });

  /**
   * 测试套件3: 错误场景和降级测试
   * 验证需求: 8.1, 8.2, 8.3, 8.4, 8.5
   */
  describe('错误场景和降级测试', () => {
    let client: ClientSocket;

    afterEach(async () => {
      if (client?.connected) {
        client.disconnect();
      }
    });

    /**
     * 测试: 无效消息格式处理
     * 验证需求 8.5: 系统应记录详细错误日志
     */
    it('应该正确处理无效的消息格式', async () => {
      client = await createClient();
      
      // 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_error_test_1' });
      await loginPromise;
      
      // 发送无效格式的聊天消息
      const errorPromise = waitForEvent<any>(client, 'chat_error');
      client.emit('chat', { invalid: 'format' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('chat_error');
      expect(response.message).toBe('消息格式无效');
    });

    /**
     * 测试: 重复登录处理
     * 验证需求 1.1: 系统应正确处理重复登录
     */
    it('应该拒绝已登录用户的重复登录请求', async () => {
      client = await createClient();
      
      // 第一次登录
      const loginPromise1 = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_duplicate_test' });
      await loginPromise1;
      
      // 尝试重复登录
      const errorPromise = waitForEvent<any>(client, 'login_error');
      client.emit('login', { type: 'login', token: 'another_token' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('login_error');
      expect(response.message).toBe('您已经登录');
    });

    /**
     * 测试: 空Token登录处理
     * 验证需求 1.1: 系统应验证Token
     */
    it('应该拒绝空Token的登录请求', async () => {
      client = await createClient();
      
      const errorPromise = waitForEvent<any>(client, 'login_error');
      client.emit('login', { type: 'login', token: '' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('login_error');
    });

    /**
     * 测试: 缺少scope的聊天消息
     * 验证需求 4.1: 系统应正确验证消息格式
     */
    it('应该拒绝缺少scope的聊天消息', async () => {
      client = await createClient();
      
      // 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_scope_test' });
      await loginPromise;
      
      // 发送缺少scope的消息
      const errorPromise = waitForEvent<any>(client, 'chat_error');
      client.emit('chat', { t: 'chat', content: 'Hello' });
      
      const response = await errorPromise;
      
      expect(response.t).toBe('chat_error');
      expect(response.message).toBe('消息格式无效');
    });

    /**
     * 测试: 无效位置更新处理
     * 验证需求 6.2: 系统应正确验证位置数据
     */
    it('应该忽略无效的位置更新', async () => {
      client = await createClient();
      
      // 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_position_error_test' });
      await loginPromise;
      
      // 发送无效位置更新（缺少mapId）
      client.emit('position_update', { t: 'position', x: 100, y: 200 });
      
      // 等待处理
      await new Promise(resolve => setTimeout(resolve, 100));
      
      // 连接应该保持正常
      expect(client.connected).toBe(true);
    });
  });

  /**
   * 测试套件4: 连接生命周期测试
   * 验证需求: 1.3, 1.4, 6.4
   */
  describe('连接生命周期测试', () => {
    /**
     * 测试: 连接-登录-断开完整流程
     * 验证需求 1.1, 1.2, 1.3: 完整的连接生命周期
     */
    it('应该正确处理完整的连接生命周期', async () => {
      // 1. 建立连接
      const client = await createClient();
      expect(client.connected).toBe(true);
      
      // 2. 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_lifecycle_test' });
      const loginResponse = await loginPromise;
      expect(loginResponse.t).toBe('login_success');
      
      // 3. 发送消息
      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { t: 'chat', content: 'Lifecycle test message', scope: 'local' });
      const chatResponse = await chatPromise;
      expect(chatResponse.t).toBe('chat_sent');
      
      // 4. 断开连接
      client.disconnect();
      
      // 等待断开完成
      await new Promise(resolve => setTimeout(resolve, 100));
      expect(client.connected).toBe(false);
    });

    /**
     * 测试: 快速连接断开
     * 验证需求 1.3: 系统应正确处理快速断开
     */
    it('应该正确处理快速连接断开', async () => {
      const client = await createClient();
      expect(client.connected).toBe(true);
      
      // 立即断开
      client.disconnect();
      
      await new Promise(resolve => setTimeout(resolve, 100));
      expect(client.connected).toBe(false);
    });

    /**
     * 测试: 登录后立即断开
     * 验证需求 1.3: 系统应清理会话资源
     */
    it('应该正确处理登录后立即断开', async () => {
      const client = await createClient();
      
      // 登录
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: 'valid_quick_disconnect' });
      await loginPromise;
      
      // 立即断开
      client.disconnect();
      
      await new Promise(resolve => setTimeout(resolve, 200));
      expect(client.connected).toBe(false);
    });
  });

  /**
   * 测试套件5: 消息格式验证测试
   * 验证需求: 5.3, 5.4
   */
  describe('消息格式验证测试', () => {
    let client: ClientSocket;
    let testId: number = 0;

    afterEach(async () => {
      if (client?.connected) {
        client.disconnect();
      }
      // 等待清理完成
      await new Promise(resolve => setTimeout(resolve, 100));
    });

    /**
     * 测试: 正常消息格式
     * 验证需求 5.3, 5.4: 消息应包含所有必需信息
     */
    it('应该接受正确格式的聊天消息', async () => {
      testId++;
      client = await createClient();
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: `valid_format_normal_${Date.now()}_${testId}` });
      await loginPromise;

      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { 
        t: 'chat', 
        content: 'Test message with correct format', 
        scope: 'local' 
      });
      
      const response = await chatPromise;
      expect(response.t).toBe('chat_sent');
    });

    /**
     * 测试: 长消息处理
     * 验证需求 4.1: 系统应正确处理各种长度的消息
     */
    it('应该正确处理较长的消息内容', async () => {
      testId++;
      client = await createClient();
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: `valid_format_long_${Date.now()}_${testId}` });
      await loginPromise;

      // 使用不重复的长消息内容，避免触发重复字符检测
      const longContent = '这是一条较长的测试消息，用于验证系统能够正确处理长消息内容。' +
        '消息系统应该能够处理各种长度的消息，包括较长的消息。' +
        '这条消息包含多种字符和标点符号，以确保系统的兼容性。' +
        '测试消息继续延长，以达到足够的长度进行测试。' +
        '系统应该能够正确处理这样的消息而不会出现问题。';
      
      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { t: 'chat', content: longContent, scope: 'local' });
      
      const response = await chatPromise;
      expect(response.t).toBe('chat_sent');
    });

    /**
     * 测试: 特殊字符消息
     * 验证需求 4.1: 系统应正确处理特殊字符
     */
    it('应该正确处理包含特殊字符的消息', async () => {
      testId++;
      client = await createClient();
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: `valid_format_special_${Date.now()}_${testId}` });
      await loginPromise;

      const specialContent = '你好！@#$%^&*()_+{}|:"<>?~`-=[]\\;\',./';
      
      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { t: 'chat', content: specialContent, scope: 'local' });
      
      const response = await chatPromise;
      expect(response.t).toBe('chat_sent');
    });

    /**
     * 测试: Unicode消息
     * 验证需求 4.1: 系统应正确处理Unicode字符
     */
    it('应该正确处理Unicode消息', async () => {
      testId++;
      client = await createClient();
      const loginPromise = waitForEvent<any>(client, 'login_success');
      client.emit('login', { type: 'login', token: `valid_format_unicode_${Date.now()}_${testId}` });
      await loginPromise;

      const unicodeContent = '🎮 游戏消息 🎯 测试 🚀';
      
      const chatPromise = waitForEvent<any>(client, 'chat_sent');
      client.emit('chat', { t: 'chat', content: unicodeContent, scope: 'local' });
      
      const response = await chatPromise;
      expect(response.t).toBe('chat_sent');
    });
  });
});
