/**
 * Zulip事件处理服务测试
 * 
 * 功能描述：
 * - 测试ZulipEventProcessorService的核心功能
 * - 包含属性测试验证消息格式转换完整性
 * - 包含属性测试验证消息接收和分发
 * 
 * **Feature: zulip-integration, Property 4: 消息格式转换完整性**
 * **Validates: Requirements 5.3, 5.4**
 * 
 * **Feature: zulip-integration, Property 5: 消息接收和分发**
 * **Validates: Requirements 5.1, 5.2, 5.5**
 * 
 * 更新记录：
 * - 2026-01-14: 重构后更新 - 使用 ISessionQueryService 接口替代具体实现
 * 
 * @author angjustinl
 * @version 2.0.0
 * @since 2025-12-25
 * @lastModified 2026-01-14
 */

import { Test, TestingModule } from '@nestjs/testing';
import * as fc from 'fast-check';
import { 
  ZulipEventProcessorService, 
  ZulipMessage, 
  GameMessage,
  MessageDistributor,
} from './zulip_event_processor.service';
import { 
  ISessionQueryService, 
  IGameSession,
  SESSION_QUERY_SERVICE,
} from '../../../core/session_core/session_core.interfaces';
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip_core/zulip_core.interfaces';

// 为测试定义 GameSession 类型别名
type GameSession = IGameSession;

describe('ZulipEventProcessorService', () => {
  let service: ZulipEventProcessorService;
  let mockSessionManager: jest.Mocked<ISessionQueryService>;
  let mockConfigManager: jest.Mocked<IZulipConfigService>;
  let mockClientPool: jest.Mocked<IZulipClientPoolService>;
  let mockDistributor: jest.Mocked<MessageDistributor>;

  // 创建模拟Zulip消息
  const createMockZulipMessage = (overrides: Partial<ZulipMessage> = {}): ZulipMessage => ({
    id: Math.floor(Math.random() * 1000000),
    sender_email: 'test@example.com',
    sender_full_name: 'Test User',
    content: 'Hello, World!',
    stream_id: 1,
    subject: 'General',
    timestamp: Math.floor(Date.now() / 1000),
    display_recipient: 'Tavern',
    type: 'stream',
    ...overrides,
  });

  // 创建模拟会话
  const createMockSession = (overrides: Partial<GameSession> = {}): GameSession => ({
    socketId: `socket_${Math.random().toString(36).substr(2, 9)}`,
    userId: `user_${Math.random().toString(36).substr(2, 9)}`,
    username: 'TestPlayer',
    zulipQueueId: `queue_${Math.random().toString(36).substr(2, 9)}`,
    currentMap: 'tavern',
    position: { x: 100, y: 200 },
    lastActivity: new Date(),
    createdAt: new Date(),
    ...overrides,
  });

  beforeEach(async () => {
    jest.clearAllMocks();

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

    mockConfigManager = {
      getMapIdByStream: jest.fn(),
      getStreamByMap: jest.fn(),
      getTopicByObject: jest.fn(),
      getZulipConfig: jest.fn(),
      hasMap: jest.fn(),
      hasStream: jest.fn(),
      getAllMapIds: jest.fn(),
      getAllStreams: jest.fn(),
      reloadConfig: jest.fn(),
      validateConfig: jest.fn(),
    } as any;

    mockClientPool = {
      getUserClient: jest.fn(),
      createUserClient: jest.fn(),
      destroyUserClient: jest.fn(),
      hasUserClient: jest.fn(),
      sendMessage: jest.fn(),
      registerEventQueue: jest.fn(),
      deregisterEventQueue: jest.fn(),
      getPoolStats: jest.fn(),
      cleanupIdleClients: jest.fn(),
    } as any;

    mockDistributor = {
      sendChatRender: jest.fn(),
      broadcastToMap: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ZulipEventProcessorService,
        {
          provide: SESSION_QUERY_SERVICE,
          useValue: mockSessionManager,
        },
        {
          provide: 'ZULIP_CONFIG_SERVICE',
          useValue: mockConfigManager,
        },
        {
          provide: 'ZULIP_CLIENT_POOL_SERVICE',
          useValue: mockClientPool,
        },
      ],
    }).compile();

    service = module.get<ZulipEventProcessorService>(ZulipEventProcessorService);
    service.setMessageDistributor(mockDistributor);
  });

  afterEach(async () => {
    await service.stopEventProcessing();
  });

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

  describe('convertMessageFormat - 消息格式转换', () => {
    it('应该正确转换基本的Zulip消息', async () => {
      const zulipMessage = createMockZulipMessage({
        sender_full_name: 'Alice',
        content: 'Hello everyone!',
      });

      const result = await service.convertMessageFormat(zulipMessage);

      expect(result.t).toBe('chat_render');
      expect(result.from).toBe('Alice');
      expect(result.txt).toBe('Hello everyone!');
      expect(result.bubble).toBe(true);
    });

    it('应该从邮箱提取用户名当sender_full_name为空时', async () => {
      const zulipMessage = createMockZulipMessage({
        sender_full_name: '',
        sender_email: 'bob@example.com',
        content: 'Test message',
      });

      const result = await service.convertMessageFormat(zulipMessage);

      expect(result.from).toBe('bob');
    });

    it('应该移除Markdown格式', async () => {
      const zulipMessage = createMockZulipMessage({
        content: '**bold** and *italic* and `code`',
      });

      const result = await service.convertMessageFormat(zulipMessage);

      expect(result.txt).toBe('bold and italic and code');
    });

    it('应该截断过长的消息', async () => {
      const longContent = 'A'.repeat(300);
      const zulipMessage = createMockZulipMessage({
        content: longContent,
      });

      const result = await service.convertMessageFormat(zulipMessage);

      expect(result.txt.length).toBeLessThanOrEqual(200);
      expect(result.txt.endsWith('...')).toBe(true);
    });
  });

  /**
   * 属性测试: 消息格式转换完整性
   * 
   * **Feature: zulip-integration, Property 4: 消息格式转换完整性**
   * **Validates: Requirements 5.3, 5.4**
   */
  describe('Property 4: 消息格式转换完整性', () => {
    it('对于任何有效的Zulip消息，转换后应该包含发送者信息', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          fc.emailAddress(),
          fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
          async (senderName, senderEmail, content) => {
            const zulipMessage = createMockZulipMessage({
              sender_full_name: senderName.trim(),
              sender_email: senderEmail,
              content: content.trim(),
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result.t).toBe('chat_render');
            expect(result.from).toBeDefined();
            expect(result.from.length).toBeGreaterThan(0);
            expect(result.from).toBe(senderName.trim());
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何sender_full_name为空的消息，应该从邮箱提取用户名', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 1, maxLength: 30 })
            .filter(s => s.trim().length > 0 && /^[a-zA-Z0-9._-]+$/.test(s)),
          fc.constantFrom('example.com', 'test.org', 'mail.net'),
          fc.string({ minLength: 1, maxLength: 200 }).filter(s => s.trim().length > 0),
          async (username, domain, content) => {
            const email = `${username}@${domain}`;
            const zulipMessage = createMockZulipMessage({
              sender_full_name: '',
              sender_email: email,
              content: content.trim(),
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result.from).toBe(username);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何消息内容，转换后应该保留核心文本信息', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 1, maxLength: 150 })
            .filter(s => {
              const trimmed = s.trim();
              return trimmed.length > 0 && 
                     !/[*_`#\[\]<>]/.test(trimmed) &&
                     !trimmed.startsWith('>') &&
                     !trimmed.startsWith('-') &&
                     !trimmed.startsWith('+') &&
                     !/^\d+\./.test(trimmed);
            }),
          async (content) => {
            const zulipMessage = createMockZulipMessage({
              content: content.trim(),
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result.txt).toBeDefined();
            expect(result.txt.length).toBeGreaterThan(0);
            
            if (content.trim().length <= 200) {
              expect(result.txt).toBe(content.trim());
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何超过200字符的消息，应该被截断并添加省略号', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '.split('')), { minLength: 250, maxLength: 500 })
            .map(arr => arr.join('')),
          async (content: string) => {
            const zulipMessage = createMockZulipMessage({
              content: content,
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result.txt.length).toBeLessThanOrEqual(200);
            expect(result.txt.endsWith('...')).toBe(true);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何包含Markdown的消息，应该正确移除格式标记', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.split('')), { minLength: 1, maxLength: 50 })
            .map(arr => arr.join('')),
          fc.constantFrom('bold', 'italic', 'code', 'link'),
          async (text: string, formatType: string) => {
            if (text.length === 0) return;
            
            let markdownContent: string;
            
            switch (formatType) {
              case 'bold':
                markdownContent = `**${text}**`;
                break;
              case 'italic':
                markdownContent = `_${text}_`;
                break;
              case 'code':
                markdownContent = `\`${text}\``;
                break;
              case 'link':
                markdownContent = `[${text}](https://example.com)`;
                break;
              default:
                markdownContent = text;
            }

            const zulipMessage = createMockZulipMessage({
              content: markdownContent,
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result.txt).toBe(text);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何消息，转换结果应该符合游戏协议格式', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.record({
            sender_full_name: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
            sender_email: fc.emailAddress(),
            content: fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
            timestamp: fc.integer({ min: 1000000000, max: 2000000000 }),
            subject: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          }),
          async (props) => {
            const zulipMessage = createMockZulipMessage({
              sender_full_name: props.sender_full_name.trim(),
              sender_email: props.sender_email,
              content: props.content.trim(),
              timestamp: props.timestamp,
              subject: props.subject.trim(),
            });

            const result = await service.convertMessageFormat(zulipMessage);

            expect(result).toHaveProperty('t', 'chat_render');
            expect(result).toHaveProperty('from');
            expect(result).toHaveProperty('txt');
            expect(result).toHaveProperty('bubble');
            
            expect(typeof result.t).toBe('string');
            expect(typeof result.from).toBe('string');
            expect(typeof result.txt).toBe('string');
            expect(typeof result.bubble).toBe('boolean');
            expect(result.bubble).toBe(true);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);
  });

  describe('determineTargetPlayers - 确定目标玩家', () => {
    it('应该根据Stream名称确定目标地图并获取玩家列表', async () => {
      const zulipMessage = createMockZulipMessage({
        display_recipient: 'Tavern',
      });

      mockConfigManager.getMapIdByStream.mockReturnValue('tavern');
      mockSessionManager.getSocketsInMap.mockResolvedValue(['socket-1', 'socket-2']);
      mockSessionManager.getSession.mockImplementation(async (socketId) => {
        if (socketId === 'socket-1') {
          return createMockSession({ socketId: 'socket-1', userId: 'user-1' });
        }
        return createMockSession({ socketId: 'socket-2', userId: 'user-2' });
      });

      const result = await service.determineTargetPlayers(zulipMessage, 'Tavern', 'sender-user');

      expect(mockConfigManager.getMapIdByStream).toHaveBeenCalledWith('Tavern');
      expect(mockSessionManager.getSocketsInMap).toHaveBeenCalledWith('tavern');
      expect(result).toContain('socket-1');
      expect(result).toContain('socket-2');
    });

    it('应该排除消息发送者', async () => {
      const zulipMessage = createMockZulipMessage({
        display_recipient: 'Tavern',
      });

      mockConfigManager.getMapIdByStream.mockReturnValue('tavern');
      mockSessionManager.getSocketsInMap.mockResolvedValue(['socket-1', 'socket-2']);
      mockSessionManager.getSession.mockImplementation(async (socketId) => {
        if (socketId === 'socket-1') {
          return createMockSession({ socketId: 'socket-1', userId: 'sender-user' });
        }
        return createMockSession({ socketId: 'socket-2', userId: 'other-user' });
      });

      const result = await service.determineTargetPlayers(zulipMessage, 'Tavern', 'sender-user');

      expect(result).not.toContain('socket-1');
      expect(result).toContain('socket-2');
    });

    it('应该在未找到地图时返回空列表', async () => {
      const zulipMessage = createMockZulipMessage({
        display_recipient: 'UnknownStream',
      });

      mockConfigManager.getMapIdByStream.mockReturnValue(null);

      const result = await service.determineTargetPlayers(zulipMessage, 'UnknownStream', 'sender-user');

      expect(result).toEqual([]);
    });
  });

  describe('distributeMessage - 消息分发', () => {
    it('应该向所有目标玩家发送消息', async () => {
      const gameMessage: GameMessage = {
        t: 'chat_render',
        from: 'TestUser',
        txt: 'Hello!',
        bubble: true,
      };
      const targetPlayers = ['socket-1', 'socket-2', 'socket-3'];

      await service.distributeMessage(gameMessage, targetPlayers);

      expect(mockDistributor.sendChatRender).toHaveBeenCalledTimes(3);
      expect(mockDistributor.sendChatRender).toHaveBeenCalledWith('socket-1', 'TestUser', 'Hello!', true);
      expect(mockDistributor.sendChatRender).toHaveBeenCalledWith('socket-2', 'TestUser', 'Hello!', true);
      expect(mockDistributor.sendChatRender).toHaveBeenCalledWith('socket-3', 'TestUser', 'Hello!', true);
    });

    it('应该在没有目标玩家时不发送任何消息', async () => {
      const gameMessage: GameMessage = {
        t: 'chat_render',
        from: 'TestUser',
        txt: 'Hello!',
        bubble: true,
      };

      await service.distributeMessage(gameMessage, []);

      expect(mockDistributor.sendChatRender).not.toHaveBeenCalled();
    });
  });

  /**
   * 属性测试: 消息接收和分发
   * 
   * **Feature: zulip-integration, Property 5: 消息接收和分发**
   * **Validates: Requirements 5.1, 5.2, 5.5**
   */
  describe('Property 5: 消息接收和分发', () => {
    it('对于任何有效的Stream消息，应该正确确定目标地图', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.constantFrom('Tavern', 'Novice Village', 'Market', 'General'),
          fc.constantFrom('tavern', 'novice_village', 'market', 'general'),
          fc.array(
            fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
            { minLength: 0, maxLength: 10 }
          ),
          async (streamName, mapId, socketIds) => {
            const zulipMessage = createMockZulipMessage({
              display_recipient: streamName,
            });

            mockConfigManager.getMapIdByStream.mockReturnValue(mapId);
            mockSessionManager.getSocketsInMap.mockResolvedValue(socketIds);
            mockSessionManager.getSession.mockImplementation(async (socketId) => {
              return createMockSession({ 
                socketId, 
                userId: `user_${socketId}`,
                currentMap: mapId,
              });
            });

            const result = await service.determineTargetPlayers(
              zulipMessage, 
              streamName, 
              'different-sender'
            );

            expect(mockConfigManager.getMapIdByStream).toHaveBeenCalledWith(streamName);
            
            if (socketIds.length > 0) {
              expect(mockSessionManager.getSocketsInMap).toHaveBeenCalledWith(mapId);
            }
            
            expect(result.length).toBe(socketIds.length);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何消息分发，发送者应该被排除在接收者之外', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
          fc.array(
            fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
            { minLength: 1, maxLength: 5 }
          ),
          async (senderUserId, otherUserIds) => {
            const zulipMessage = createMockZulipMessage({
              display_recipient: 'Tavern',
            });

            const allSocketIds = [`socket_${senderUserId}`, ...otherUserIds.map(id => `socket_${id}`)];
            
            mockConfigManager.getMapIdByStream.mockReturnValue('tavern');
            mockSessionManager.getSocketsInMap.mockResolvedValue(allSocketIds);
            mockSessionManager.getSession.mockImplementation(async (socketId) => {
              const userId = socketId.replace('socket_', '');
              return createMockSession({ 
                socketId, 
                userId,
              });
            });

            const result = await service.determineTargetPlayers(
              zulipMessage, 
              'Tavern', 
              senderUserId
            );

            expect(result).not.toContain(`socket_${senderUserId}`);
            
            for (const userId of otherUserIds) {
              expect(result).toContain(`socket_${userId}`);
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何消息分发，所有目标玩家都应该收到消息', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0),
          fc.string({ minLength: 1, maxLength: 200 }).filter(s => s.trim().length > 0),
          fc.array(
            fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
            { minLength: 1, maxLength: 10 }
          ),
          async (from, txt, targetPlayers) => {
            const gameMessage: GameMessage = {
              t: 'chat_render',
              from: from.trim(),
              txt: txt.trim(),
              bubble: true,
            };

            mockDistributor.sendChatRender.mockClear();

            await service.distributeMessage(gameMessage, targetPlayers);

            expect(mockDistributor.sendChatRender).toHaveBeenCalledTimes(targetPlayers.length);
            
            for (const socketId of targetPlayers) {
              expect(mockDistributor.sendChatRender).toHaveBeenCalledWith(
                socketId,
                from.trim(),
                txt.trim(),
                true
              );
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('对于任何未知Stream的消息，应该返回空的目标玩家列表', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.string({ minLength: 5, maxLength: 50 })
            .filter(s => s.trim().length > 0)
            .map(s => `Unknown_${s}`),
          async (unknownStream) => {
            const zulipMessage = createMockZulipMessage({
              display_recipient: unknownStream,
            });

            mockConfigManager.getMapIdByStream.mockReturnValue(null);

            const result = await service.determineTargetPlayers(
              zulipMessage, 
              unknownStream, 
              'sender-user'
            );

            expect(result).toEqual([]);
            expect(mockSessionManager.getSocketsInMap).not.toHaveBeenCalled();
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    it('完整的消息处理流程应该正确执行', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.record({
            senderName: fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0),
            senderEmail: fc.emailAddress(),
            senderUserId: fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
          }),
          fc.string({ minLength: 1, maxLength: 150 }).filter(s => s.trim().length > 0),
          fc.constantFrom('Tavern', 'Novice Village'),
          fc.integer({ min: 1, max: 5 }),
          async (sender, content, streamName, playerCount) => {
            const zulipMessage = createMockZulipMessage({
              sender_full_name: sender.senderName.trim(),
              sender_email: sender.senderEmail,
              content: content.trim(),
              display_recipient: streamName,
            });

            const targetSocketIds = Array.from(
              { length: playerCount }, 
              (_, i) => `socket_player_${i}`
            );

            const mapId = streamName.toLowerCase().replace(' ', '_');
            
            mockConfigManager.getMapIdByStream.mockReturnValue(mapId);
            mockSessionManager.getSocketsInMap.mockResolvedValue(targetSocketIds);
            mockSessionManager.getSession.mockImplementation(async (socketId) => {
              return createMockSession({ 
                socketId, 
                userId: socketId.replace('socket_', 'user_'),
              });
            });

            mockDistributor.sendChatRender.mockClear();

            const result = await service.processMessageManually(zulipMessage, sender.senderUserId);

            expect(result.success).toBe(true);
            expect(result.targetCount).toBe(playerCount);
            expect(mockDistributor.sendChatRender).toHaveBeenCalledTimes(playerCount);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);
  });

  describe('getProcessingStats - 获取处理统计', () => {
    it('应该返回正确的统计信息', () => {
      const stats = service.getProcessingStats();

      expect(stats).toHaveProperty('isActive');
      expect(stats).toHaveProperty('activeQueues');
      expect(stats).toHaveProperty('totalQueues');
      expect(stats).toHaveProperty('queueIds');
      expect(stats).toHaveProperty('processedEvents');
      expect(stats).toHaveProperty('processedMessages');
    });
  });

  describe('registerEventQueue / unregisterEventQueue - 队列管理', () => {
    it('应该正确注册和注销事件队列', async () => {
      const queueId = 'test-queue-123';
      const userId = 'user-456';

      await service.registerEventQueue(queueId, userId, 0);
      
      let stats = service.getProcessingStats();
      expect(stats.queueIds).toContain(queueId);
      expect(stats.totalQueues).toBe(1);

      await service.unregisterEventQueue(queueId);
      
      stats = service.getProcessingStats();
      expect(stats.queueIds).not.toContain(queueId);
      expect(stats.totalQueues).toBe(0);
    });
  });
});
