/**
 * 聊天 WebSocket 网关单元测试
 * 
 * 功能描述：
 * - 测试 ChatWebSocketGateway 的 WebSocket 连接管理
 * - 验证消息路由和处理逻辑
 * - 测试房间管理和广播功能
 * 
 * 测试范围：
 * - onModuleInit() - 模块初始化
 * - onModuleDestroy() - 模块销毁
 * - getConnectionCount() - 获取连接数
 * - getAuthenticatedConnectionCount() - 获取认证连接数
 * - getMapPlayerCounts() - 获取地图玩家数
 * - getMapPlayers() - 获取地图玩家列表
 * - sendToPlayer() - 单播消息
 * - broadcastToMap() - 地图广播
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-14
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ChatWebSocketGateway } from './chat.gateway';
import { ChatService } from '../../business/chat/chat.service';

// Mock ws module
jest.mock('ws', () => {
  const mockServerInstance = {
    on: jest.fn(),
    close: jest.fn(),
  };
  
  const MockServer = jest.fn(() => mockServerInstance);
  
  return {
    Server: MockServer,
    OPEN: 1,
    __mockServerInstance: mockServerInstance,
  };
});

describe('ChatWebSocketGateway', () => {
  let gateway: ChatWebSocketGateway;
  let mockChatService: jest.Mocked<Partial<ChatService>>;

  beforeEach(async () => {
    // Reset mocks
    jest.clearAllMocks();
    
    mockChatService = {
      setWebSocketGateway: jest.fn(),
      handlePlayerLogin: jest.fn(),
      handlePlayerLogout: jest.fn(),
      sendChatMessage: jest.fn(),
      updatePlayerPosition: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ChatWebSocketGateway,
        { provide: ChatService, useValue: mockChatService },
      ],
    }).compile();

    gateway = module.get<ChatWebSocketGateway>(ChatWebSocketGateway);
  });

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

  describe('onModuleInit', () => {
    it('should initialize WebSocket server and set gateway reference', async () => {
      await gateway.onModuleInit();

      expect(mockChatService.setWebSocketGateway).toHaveBeenCalledWith(gateway);
    });

    it('should use default port 3001 when WEBSOCKET_PORT is not set', async () => {
      delete process.env.WEBSOCKET_PORT;

      await gateway.onModuleInit();

      // Verify server was created (mock was called)
      const ws = require('ws');
      expect(ws.Server).toHaveBeenCalledWith(
        expect.objectContaining({
          port: 3001,
          path: '/game',
        })
      );
    });

    it('should use custom port from environment variable', async () => {
      process.env.WEBSOCKET_PORT = '4000';

      // Create new gateway instance to pick up env change
      const module: TestingModule = await Test.createTestingModule({
        providers: [
          ChatWebSocketGateway,
          { provide: ChatService, useValue: mockChatService },
        ],
      }).compile();

      const newGateway = module.get<ChatWebSocketGateway>(ChatWebSocketGateway);
      await newGateway.onModuleInit();

      const ws = require('ws');
      expect(ws.Server).toHaveBeenCalledWith(
        expect.objectContaining({
          port: 4000,
          path: '/game',
        })
      );

      delete process.env.WEBSOCKET_PORT;
    });
  });

  describe('onModuleDestroy', () => {
    it('should close WebSocket server when it exists', async () => {
      await gateway.onModuleInit();
      await gateway.onModuleDestroy();

      const ws = require('ws');
      expect(ws.__mockServerInstance.close).toHaveBeenCalled();
    });

    it('should not throw when server does not exist', async () => {
      // Don't call onModuleInit, so server is undefined
      await expect(gateway.onModuleDestroy()).resolves.not.toThrow();
    });
  });

  describe('getConnectionCount', () => {
    it('should return 0 when no clients connected', () => {
      expect(gateway.getConnectionCount()).toBe(0);
    });
  });

  describe('getAuthenticatedConnectionCount', () => {
    it('should return 0 when no authenticated clients', () => {
      expect(gateway.getAuthenticatedConnectionCount()).toBe(0);
    });
  });

  describe('getMapPlayerCounts', () => {
    it('should return empty object when no rooms exist', () => {
      expect(gateway.getMapPlayerCounts()).toEqual({});
    });
  });

  describe('getMapPlayers', () => {
    it('should return empty array for non-existent room', () => {
      expect(gateway.getMapPlayers('non_existent_map')).toEqual([]);
    });
  });

  describe('sendToPlayer', () => {
    it('should not throw when client does not exist', () => {
      expect(() => {
        gateway.sendToPlayer('non_existent_id', { type: 'test' });
      }).not.toThrow();
    });
  });

  describe('broadcastToMap', () => {
    it('should not throw when room does not exist', () => {
      expect(() => {
        gateway.broadcastToMap('non_existent_map', { type: 'test' });
      }).not.toThrow();
    });

    it('should handle excludeId parameter', () => {
      expect(() => {
        gateway.broadcastToMap('non_existent_map', { type: 'test' }, 'exclude_id');
      }).not.toThrow();
    });
  });

  describe('IChatWebSocketGateway interface', () => {
    it('should implement all interface methods', () => {
      expect(typeof gateway.sendToPlayer).toBe('function');
      expect(typeof gateway.broadcastToMap).toBe('function');
      expect(typeof gateway.getConnectionCount).toBe('function');
      expect(typeof gateway.getAuthenticatedConnectionCount).toBe('function');
      expect(typeof gateway.getMapPlayerCounts).toBe('function');
      expect(typeof gateway.getMapPlayers).toBe('function');
    });
  });
});
