/**
 * WebSocket网关测试
 * 
 * 功能描述：
 * - 测试WebSocket连接管理功能
 * - 验证消息广播和路由逻辑
 * - 测试用户认证和会话管理
 * - 验证错误处理和连接清理
 * 
 * 测试范围：
 * - 连接建立和断开测试
 * - 消息处理和广播测试
 * - 用户认证测试
 * - 错误处理测试
 * 
 * 最近修改：
 * - 2026-01-12: 测试修复 - 修正私有方法访问和接口匹配问题，适配实际网关实现 (修改者: moyin)
 * - 2026-01-12: 代码规范优化 - 创建测试文件，确保WebSocket网关功能的测试覆盖 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.1.0
 * @since 2026-01-12
 * @lastModified 2026-01-12
 */

import { Test, TestingModule } from '@nestjs/testing';
import { WsException } from '@nestjs/websockets';
import { CleanWebSocketGateway } from './clean_websocket.gateway';
import { SessionManagerService } from './services/session_manager.service';
import { MessageFilterService } from './services/message_filter.service';
import { ZulipService } from './zulip.service';

describe('CleanWebSocketGateway', () => {
  let gateway: CleanWebSocketGateway;
  let sessionManagerService: jest.Mocked<SessionManagerService>;
  let messageFilterService: jest.Mocked<MessageFilterService>;
  let zulipService: jest.Mocked<ZulipService>;

  const mockSocket = {
    id: 'socket123',
    emit: jest.fn(),
    disconnect: jest.fn(),
    handshake: {
      auth: { token: 'valid-jwt-token' },
      headers: { authorization: 'Bearer valid-jwt-token' },
    },
    data: {},
  };

  const mockServer = {
    emit: jest.fn(),
    to: jest.fn().mockReturnThis(),
    in: jest.fn().mockReturnThis(),
    sockets: new Map(),
  };

  beforeEach(async () => {
    const mockSessionManagerService = {
      createSession: jest.fn(),
      destroySession: jest.fn(),
      getSession: jest.fn(),
      updateSession: jest.fn(),
      validateSession: jest.fn(),
    };

    const mockMessageFilterService = {
      filterMessage: jest.fn(),
      validateMessageContent: jest.fn(),
      checkRateLimit: jest.fn(),
    };

    const mockZulipService = {
      handlePlayerLogin: jest.fn(),
      handlePlayerLogout: jest.fn(),
      sendChatMessage: jest.fn(),
      setWebSocketGateway: jest.fn(),
    };

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

    gateway = module.get<CleanWebSocketGateway>(CleanWebSocketGateway);
    sessionManagerService = module.get(SessionManagerService);
    messageFilterService = module.get(MessageFilterService);
    zulipService = module.get(ZulipService);

    // Reset all mocks
    jest.clearAllMocks();
  });

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

    it('should have all required dependencies', () => {
      expect(sessionManagerService).toBeDefined();
      expect(messageFilterService).toBeDefined();
      expect(zulipService).toBeDefined();
    });
  });

  describe('handleConnection', () => {
    it('should accept valid connection with JWT token', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        emit: jest.fn(),
        disconnect: jest.fn(),
        handshake: {
          auth: { token: 'valid-jwt-token' },
          headers: { authorization: 'Bearer valid-jwt-token' },
        },
        data: {},
        readyState: 1, // WebSocket.OPEN
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      // Mock the private method calls by testing the public interface
      // Since handleConnection is private, we test through the message handling
      const loginMessage = {
        type: 'login',
        token: 'valid-jwt-token'
      };

      zulipService.handlePlayerLogin.mockResolvedValue({
        success: true,
        userId: 'user123',
        username: 'testuser',
        sessionId: 'session123',
      });

      // Act - Test through public interface
      await gateway['handleMessage'](mockSocket as any, loginMessage);

      // Assert
      expect(zulipService.handlePlayerLogin).toHaveBeenCalledWith({
        socketId: mockSocket.id,
        token: 'valid-jwt-token'
      });
    });

    it('should reject connection with invalid JWT token', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        emit: jest.fn(),
        disconnect: jest.fn(),
        handshake: {
          auth: { token: 'invalid-token' },
          headers: { authorization: 'Bearer invalid-token' },
        },
        data: {},
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      const loginMessage = {
        type: 'login',
        token: 'invalid-token'
      };

      zulipService.handlePlayerLogin.mockResolvedValue({
        success: false,
        error: 'Invalid token',
      });

      // Act
      await gateway['handleMessage'](mockSocket as any, loginMessage);

      // Assert
      expect(zulipService.handlePlayerLogin).toHaveBeenCalledWith({
        socketId: mockSocket.id,
        token: 'invalid-token'
      });
    });

    it('should reject connection without token', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        emit: jest.fn(),
        disconnect: jest.fn(),
        handshake: {
          auth: {},
          headers: {},
        },
        data: {},
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      const loginMessage = {
        type: 'login',
        // No token
      };

      // Act & Assert - Should send error message
      await gateway['handleMessage'](mockSocket as any, loginMessage);
      
      expect(mockSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          type: 'error',
          message: 'Token不能为空'
        })
      );
    });
  });

  describe('handleDisconnect', () => {
    it('should clean up session on disconnect', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        authenticated: true,
        username: 'testuser',
        currentMap: 'whale_port',
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      // Act - Test through the cleanup method since handleDisconnect is private
      await gateway['cleanupClient'](mockSocket as any, 'disconnect');

      // Assert
      expect(zulipService.handlePlayerLogout).toHaveBeenCalledWith(mockSocket.id, 'disconnect');
    });

    it('should handle disconnect when session does not exist', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        authenticated: false,
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      // Act
      await gateway['cleanupClient'](mockSocket as any, 'disconnect');

      // Assert - Should not call logout for unauthenticated users
      expect(zulipService.handlePlayerLogout).not.toHaveBeenCalled();
    });

    it('should handle errors during session cleanup', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        authenticated: true,
        username: 'testuser',
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      zulipService.handlePlayerLogout.mockRejectedValue(new Error('Cleanup failed'));

      // Act & Assert - Should not throw, just log error
      await expect(gateway['cleanupClient'](mockSocket as any, 'disconnect')).resolves.not.toThrow();
    });
  });

  describe('handleMessage', () => {
    const validMessage = {
      type: 'chat',
      content: 'Hello, world!',
      scope: 'local',
    };

    const mockSocket = {
      id: 'socket123',
      authenticated: true,
      userId: 'user123',
      username: 'testuser',
      readyState: 1,
      send: jest.fn(),
      close: jest.fn(),
      on: jest.fn(),
    };

    it('should process valid chat message', async () => {
      // Arrange
      zulipService.sendChatMessage.mockResolvedValue({
        success: true,
        messageId: 'msg123',
      });

      // Act
      await gateway['handleMessage'](mockSocket as any, validMessage);

      // Assert
      expect(zulipService.sendChatMessage).toHaveBeenCalledWith({
        socketId: mockSocket.id,
        content: validMessage.content,
        scope: validMessage.scope,
      });
    });

    it('should reject message from unauthenticated user', async () => {
      // Arrange
      const unauthenticatedSocket = {
        ...mockSocket,
        authenticated: false,
      };

      // Act
      await gateway['handleMessage'](unauthenticatedSocket as any, validMessage);

      // Assert
      expect(unauthenticatedSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          type: 'error',
          message: '请先登录'
        })
      );
    });

    it('should reject message with empty content', async () => {
      // Arrange
      const emptyMessage = {
        type: 'chat',
        content: '',
        scope: 'local',
      };

      // Act
      await gateway['handleMessage'](mockSocket as any, emptyMessage);

      // Assert
      expect(mockSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          type: 'error',
          message: '消息内容不能为空'
        })
      );
    });

    it('should handle zulip service errors during message sending', async () => {
      // Arrange
      zulipService.sendChatMessage.mockResolvedValue({
        success: false,
        error: 'Zulip API error',
      });

      // Act
      await gateway['handleMessage'](mockSocket as any, validMessage);

      // Assert
      expect(mockSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          t: 'chat_error',
          message: 'Zulip API error'
        })
      );
    });
  });

  describe('broadcastToMap', () => {
    it('should broadcast message to specific map', () => {
      // Arrange
      const message = {
        t: 'chat',
        content: 'Hello room!',
        from: 'user123',
        timestamp: new Date().toISOString(),
      };
      const mapId = 'whale_port';

      // Act
      gateway.broadcastToMap(mapId, message);

      // Assert - Since we can't easily test the internal map structure,
      // we just verify the method doesn't throw
      expect(true).toBe(true);
    });
  });

  describe('Error Handling', () => {
    it('should handle authentication errors gracefully', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      const loginMessage = {
        type: 'login',
        token: 'valid-token'
      };

      zulipService.handlePlayerLogin.mockRejectedValue(new Error('Auth service unavailable'));

      // Act
      await gateway['handleMessage'](mockSocket as any, loginMessage);

      // Assert
      expect(mockSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          type: 'error',
          message: '登录处理失败'
        })
      );
    });

    it('should handle message processing errors', async () => {
      // Arrange
      const mockSocket = {
        id: 'socket123',
        authenticated: true,
        readyState: 1,
        send: jest.fn(),
        close: jest.fn(),
        on: jest.fn(),
      };

      const validMessage = {
        type: 'chat',
        content: 'Hello, world!',
      };

      zulipService.sendChatMessage.mockRejectedValue(new Error('Service error'));

      // Act
      await gateway['handleMessage'](mockSocket as any, validMessage);

      // Assert
      expect(mockSocket.send).toHaveBeenCalledWith(
        JSON.stringify({
          type: 'error',
          message: '聊天处理失败'
        })
      );
    });
  });

  describe('Connection Management', () => {
    it('should track active connections', () => {
      // Act
      const connectionCount = gateway.getConnectionCount();

      // Assert
      expect(typeof connectionCount).toBe('number');
      expect(connectionCount).toBeGreaterThanOrEqual(0);
    });

    it('should track authenticated connections', () => {
      // Act
      const authCount = gateway.getAuthenticatedConnectionCount();

      // Assert
      expect(typeof authCount).toBe('number');
      expect(authCount).toBeGreaterThanOrEqual(0);
    });

    it('should get map player counts', () => {
      // Act
      const mapCounts = gateway.getMapPlayerCounts();

      // Assert
      expect(typeof mapCounts).toBe('object');
    });

    it('should get players in specific map', () => {
      // Act
      const players = gateway.getMapPlayers('whale_port');

      // Assert
      expect(Array.isArray(players)).toBe(true);
    });
  });
});