/**
 * 位置广播端到端测试
 * 
 * 功能描述：
 * - 测试位置广播系统的完整功能
 * - 验证WebSocket连接和消息传递
 * - 确保会话管理和用户状态同步
 * - 测试位置更新和广播机制
 * 
 * 最近修改：
 * - 2026-01-08: 文件重命名 - 修正kebab-case为snake_case命名规范 (修改者: moyin)
 * 
 * @author original
 * @version 1.0.1
 * @since 2025-01-01
 * @lastModified 2026-01-08
 */

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { io, Socket } from 'socket.io-client';
import { LocationBroadcastModule } from '../../location_broadcast.module';

describe('LocationBroadcast (e2e)', () => {
  let app: INestApplication;
  let authToken: string;

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

    app = moduleFixture.createNestApplication();
    await app.init();
    await app.listen(0);

    authToken = 'test-jwt-token';
  });

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

  describe('WebSocket连接测试', () => {
    it('应该成功建立WebSocket连接', (done) => {
      const port = app.getHttpServer().address().port;
      const client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client.on('connect', () => {
        expect(client.connected).toBe(true);
        client.disconnect();
        done();
      });

      client.on('connect_error', (error) => {
        done(error);
      });
    });

    it('应该拒绝无效的认证令牌', (done) => {
      const port = app.getHttpServer().address().port;
      const client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: 'invalid-token' },
        transports: ['websocket'],
      });

      client.on('connect_error', (error) => {
        expect(error).toBeDefined();
        done();
      });

      client.on('connect', () => {
        client.disconnect();
        done(new Error('应该拒绝无效令牌'));
      });
    });
  });

  describe('会话管理测试', () => {
    let client: Socket;

    beforeEach((done) => {
      const port = app.getHttpServer().address().port;
      client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client.on('connect', () => {
        done();
      });
    });

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

    it('应该成功加入会话', (done) => {
      client.emit('join_session', {
        type: 'join_session',
        sessionId: 'test-session-001',
        initialPosition: { x: 100, y: 200, mapId: 'plaza' },
      });

      client.on('session_joined', (response) => {
        expect(response.success).toBe(true);
        expect(response.sessionId).toBe('test-session-001');
        done();
      });

      client.on('error', (error) => {
        done(error);
      });
    });

    it('应该成功离开会话', (done) => {
      const sessionId = 'test-session-leave';

      // 先加入会话
      client.emit('join_session', {
        type: 'join_session',
        sessionId,
        initialPosition: { x: 100, y: 200, mapId: 'plaza' },
      });

      client.on('session_joined', () => {
        // 然后离开会话
        client.emit('leave_session', {
          type: 'leave_session',
          sessionId,
        });
      });

      client.on('session_left', (response) => {
        expect(response.success).toBe(true);
        expect(response.sessionId).toBe(sessionId);
        done();
      });

      client.on('error', (error) => {
        done(error);
      });
    });
  });

  describe('位置更新测试', () => {
    let client: Socket;
    const sessionId = 'position-test-session';

    beforeEach((done) => {
      const port = app.getHttpServer().address().port;
      client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client.on('connect', () => {
        client.emit('join_session', {
          type: 'join_session',
          sessionId,
          initialPosition: { x: 100, y: 200, mapId: 'plaza' },
        });
      });

      client.on('session_joined', () => {
        done();
      });
    });

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

    it('应该成功更新位置', (done) => {
      client.emit('position_update', {
        type: 'position_update',
        x: 150,
        y: 250,
        mapId: 'plaza',
      });

      client.on('position_update_success', (response) => {
        expect(response.success).toBe(true);
        expect(response.position.x).toBe(150);
        expect(response.position.y).toBe(250);
        done();
      });

      client.on('error', (error) => {
        done(error);
      });
    });

    it('应该拒绝无效的位置数据', (done) => {
      client.emit('position_update', {
        type: 'position_update',
        x: 'invalid',
        y: 250,
        mapId: 'plaza',
      });

      client.on('error', (error) => {
        expect(error.message).toContain('Invalid position data');
        done();
      });

      client.on('position_update_success', () => {
        done(new Error('应该拒绝无效位置数据'));
      });
    });
  });

  describe('位置广播测试', () => {
    let client1: Socket;
    let client2: Socket;
    const sessionId = 'broadcast-test-session';

    beforeEach((done) => {
      const port = app.getHttpServer().address().port;
      let connectedClients = 0;

      client1 = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client2 = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      const checkConnections = () => {
        connectedClients++;
        if (connectedClients === 2) {
          // 两个客户端都加入同一会话
          client1.emit('join_session', {
            type: 'join_session',
            sessionId,
            initialPosition: { x: 100, y: 200, mapId: 'plaza' },
          });

          client2.emit('join_session', {
            type: 'join_session',
            sessionId,
            initialPosition: { x: 300, y: 400, mapId: 'plaza' },
          });
        }
      };

      let joinedClients = 0;
      const checkJoins = () => {
        joinedClients++;
        if (joinedClients === 2) {
          done();
        }
      };

      client1.on('connect', checkConnections);
      client2.on('connect', checkConnections);
      client1.on('session_joined', checkJoins);
      client2.on('session_joined', checkJoins);
    });

    afterEach(() => {
      if (client1) client1.disconnect();
      if (client2) client2.disconnect();
    });

    it('应该向同会话的其他用户广播位置更新', (done) => {
      // client2 监听广播
      client2.on('position_broadcast', (broadcast) => {
        expect(broadcast.userId).toBeDefined();
        expect(broadcast.position.x).toBe(150);
        expect(broadcast.position.y).toBe(250);
        done();
      });

      // client1 更新位置
      client1.emit('position_update', {
        type: 'position_update',
        x: 150,
        y: 250,
        mapId: 'plaza',
      });
    });

    it('不应该向不同会话的用户广播位置更新', (done) => {
      const differentSessionId = 'different-session';
      let broadcastReceived = false;

      // client2 加入不同会话
      client2.emit('leave_session', {
        type: 'leave_session',
        sessionId,
      });

      client2.on('session_left', () => {
        client2.emit('join_session', {
          type: 'join_session',
          sessionId: differentSessionId,
          initialPosition: { x: 300, y: 400, mapId: 'plaza' },
        });
      });

      client2.on('session_joined', () => {
        // 监听广播
        client2.on('position_broadcast', () => {
          broadcastReceived = true;
        });

        // client1 更新位置
        client1.emit('position_update', {
          type: 'position_update',
          x: 150,
          y: 250,
          mapId: 'plaza',
        });

        // 等待一段时间确认没有收到广播
        const timeoutId = setTimeout(() => {
          expect(broadcastReceived).toBe(false);
          done();
        }, 1000);
      });
    });
  });

  describe('心跳机制测试', () => {
    let client: Socket;

    beforeEach((done) => {
      const port = app.getHttpServer().address().port;
      client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client.on('connect', () => {
        done();
      });
    });

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

    it('应该响应心跳消息', (done) => {
      const timestamp = Date.now();

      client.emit('heartbeat', {
        type: 'heartbeat',
        timestamp,
      });

      client.on('heartbeat_response', (response) => {
        expect(response.timestamp).toBe(timestamp);
        expect(response.serverTime).toBeDefined();
        done();
      });

      client.on('error', (error) => {
        done(error);
      });
    });
  });

  describe('错误处理测试', () => {
    let client: Socket;

    beforeEach((done) => {
      const port = app.getHttpServer().address().port;
      client = io(`http://localhost:${port}/location-broadcast`, {
        auth: { token: authToken },
        transports: ['websocket'],
      });

      client.on('connect', () => {
        done();
      });
    });

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

    it('应该处理无效的消息格式', (done) => {
      client.emit('invalid_message', 'not an object');

      client.on('error', (error) => {
        expect(error.message).toContain('Invalid message format');
        done();
      });
    });

    it('应该处理未知的消息类型', (done) => {
      client.emit('unknown_type', {
        type: 'unknown_message_type',
        data: 'test',
      });

      client.on('error', (error) => {
        expect(error.message).toContain('Unknown message type');
        done();
      });
    });

    it('应该处理在未加入会话时的位置更新', (done) => {
      client.emit('position_update', {
        type: 'position_update',
        x: 100,
        y: 200,
        mapId: 'plaza',
      });

      client.on('error', (error) => {
        expect(error.message).toContain('Not in session');
        done();
      });
    });
  });
});