/**
 * 消息过滤服务测试
 * 
 * 功能描述：
 * - 测试MessageFilterService的核心功能
 * - 包含属性测试验证内容安全和频率控制
 * 
 * @author angjustinl
 * @version 1.0.0
 * @since 2025-12-25
 */

import { Test, TestingModule } from '@nestjs/testing';
import * as fc from 'fast-check';
import { MessageFilterService, ViolationType } from './message_filter.service';
import { IZulipConfigService } from '../../../core/zulip_core/zulip_core.interfaces';
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
import { IRedisService } from '../../../core/redis/redis.interface';

describe('MessageFilterService', () => {
  let service: MessageFilterService;
  let mockLogger: jest.Mocked<AppLoggerService>;
  let mockRedisService: jest.Mocked<IRedisService>;
  let mockConfigManager: jest.Mocked<IZulipConfigService>;

  // 内存存储模拟Redis
  let memoryStore: Map<string, { value: string; expireAt?: number }>;

  beforeEach(async () => {
    jest.clearAllMocks();
    
    // 初始化内存存储
    memoryStore = new Map();

    mockLogger = {
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      debug: jest.fn(),
    } as any;

    // 创建模拟Redis服务
    mockRedisService = {
      set: jest.fn().mockImplementation(async (key: string, value: string, ttl?: number) => {
        memoryStore.set(key, { 
          value, 
          expireAt: ttl ? Date.now() + ttl * 1000 : undefined 
        });
      }),
      setex: jest.fn().mockImplementation(async (key: string, ttl: number, value: string) => {
        memoryStore.set(key, { 
          value, 
          expireAt: Date.now() + ttl * 1000 
        });
      }),
      get: jest.fn().mockImplementation(async (key: string) => {
        const item = memoryStore.get(key);
        if (!item) return null;
        if (item.expireAt && item.expireAt <= Date.now()) {
          memoryStore.delete(key);
          return null;
        }
        return item.value;
      }),
      del: jest.fn().mockImplementation(async (key: string) => {
        const existed = memoryStore.has(key);
        memoryStore.delete(key);
        return existed;
      }),
      exists: jest.fn().mockImplementation(async (key: string) => {
        return memoryStore.has(key);
      }),
      ttl: jest.fn().mockImplementation(async (key: string) => {
        const item = memoryStore.get(key);
        if (!item || !item.expireAt) return -1;
        return Math.max(0, Math.floor((item.expireAt - Date.now()) / 1000));
      }),
      incr: jest.fn().mockImplementation(async (key: string) => {
        const item = memoryStore.get(key);
        if (!item) {
          memoryStore.set(key, { value: '1' });
          return 1;
        }
        const newValue = parseInt(item.value, 10) + 1;
        item.value = newValue.toString();
        return newValue;
      }),
    } as any;

    // 创建模拟ConfigManager服务
    mockConfigManager = {
      getStreamByMap: jest.fn().mockImplementation((mapId: string) => {
        const mapping: Record<string, string> = {
          'novice_village': 'Novice Village',
          'tavern': 'Tavern',
          'market': 'Market',
        };
        return mapping[mapId] || null;
      }),
      hasMap: jest.fn().mockImplementation((mapId: string) => {
        return ['novice_village', 'tavern', 'market'].includes(mapId);
      }),
      getMapIdByStream: jest.fn(),
      getTopicByObject: jest.fn(),
      getZulipConfig: jest.fn(),
      hasStream: jest.fn(),
      getAllMapIds: jest.fn(),
      getAllStreams: jest.fn(),
      reloadConfig: jest.fn(),
      validateConfig: jest.fn(),
    } as any;

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MessageFilterService,
        {
          provide: AppLoggerService,
          useValue: mockLogger,
        },
        {
          provide: 'REDIS_SERVICE',
          useValue: mockRedisService,
        },
        {
          provide: 'ZULIP_CONFIG_SERVICE',
          useValue: mockConfigManager,
        },
      ],
    }).compile();

    service = module.get<MessageFilterService>(MessageFilterService);
  });

  afterEach(async () => {
    memoryStore.clear();
  });

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

  describe('filterContent - 内容过滤', () => {
    it('应该允许正常消息通过', async () => {
      const result = await service.filterContent('Hello, world!');
      expect(result.allowed).toBe(true);
      expect(result.filtered).toBeUndefined();
    });

    it('应该拒绝空消息', async () => {
      const result = await service.filterContent('');
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('不能为空');
    });

    it('应该拒绝只包含空白字符的消息', async () => {
      const result = await service.filterContent('   \t\n  ');
      expect(result.allowed).toBe(false);
      expect(result.reason).toBeDefined();
    });

    it('应该拒绝过长的消息', async () => {
      const longMessage = 'a'.repeat(1001);
      const result = await service.filterContent(longMessage);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('过长');
    });

    it('应该替换敏感词', async () => {
      const result = await service.filterContent('这是垃圾消息');
      expect(result.allowed).toBe(true);
      expect(result.filtered).toBe('这是**消息');
    });

    it('应该拒绝包含重复字符的消息', async () => {
      const result = await service.filterContent('aaaaaaaaa');
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('重复字符');
    });
  });

  describe('checkRateLimit - 频率限制', () => {
    it('应该允许首次发送', async () => {
      const result = await service.checkRateLimit('user-123');
      expect(result).toBe(true);
    });

    it('应该在达到限制后拒绝', async () => {
      // 发送10条消息（达到限制）
      for (let i = 0; i < 10; i++) {
        await service.checkRateLimit('user-123');
      }
      
      // 第11条应该被拒绝
      const result = await service.checkRateLimit('user-123');
      expect(result).toBe(false);
    });
  });

  describe('validatePermission - 权限验证', () => {
    it('应该允许匹配的地图和Stream', async () => {
      const result = await service.validatePermission(
        'user-123',
        'Novice Village',
        'novice_village'
      );
      expect(result).toBe(true);
    });

    it('应该拒绝不匹配的地图和Stream', async () => {
      const result = await service.validatePermission(
        'user-123',
        'Tavern',
        'novice_village'
      );
      expect(result).toBe(false);
    });

    it('应该拒绝未知地图', async () => {
      const result = await service.validatePermission(
        'user-123',
        'Some Stream',
        'unknown_map'
      );
      expect(result).toBe(false);
    });
  });


  /**
   * 属性测试: 内容安全和频率控制
   * 
   * **Feature: zulip-integration, Property 7: 内容安全和频率控制**
   * **Validates: Requirements 4.3, 4.4**
   * 
   * 对于任何包含敏感词或高频发送的消息，系统应该正确过滤敏感内容，
   * 实施频率限制，并返回适当的提示信息
   */
  describe('Property 7: 内容安全和频率控制', () => {
    /**
     * 属性: 对于任何有效的非敏感消息，内容过滤应该允许通过
     * 验证需求 4.3: 消息内容包含敏感词时系统应过滤敏感内容或拒绝发送
     */
    it('对于任何有效的非敏感消息，内容过滤应该允许通过', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的非敏感消息（字母、数字、空格组成）
          fc.string({ minLength: 1, maxLength: 500 })
            .filter(s => s.trim().length > 0)
            .filter(s => !/(.)\1{4,}/.test(s)) // 排除重复字符
            .filter(s => !['垃圾', '广告', '刷屏', '傻逼', '操你'].some(w => s.includes(w))) // 排除敏感词
            .map(s => s.replace(/(.{2,})\1{2,}/g, '$1')), // 移除重复短语
          async (content) => {
            const result = await service.filterContent(content);
            
            // 有效的非敏感消息应该被允许
            if (content.trim().length > 0 && content.length <= 1000) {
              expect(result.allowed).toBe(true);
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何包含敏感词的消息，应该被过滤或拒绝
     * 验证需求 4.3: 消息内容包含敏感词时系统应过滤敏感内容或拒绝发送
     */
    it('对于任何包含敏感词的消息，应该被过滤或拒绝', async () => {
      const sensitiveWords = ['垃圾', '广告', '刷屏'];
      
      await fc.assert(
        fc.asyncProperty(
          // 生成包含敏感词的消息
          fc.constantFrom(...sensitiveWords),
          fc.string({ minLength: 0, maxLength: 50 }),
          fc.string({ minLength: 0, maxLength: 50 }),
          async (sensitiveWord, prefix, suffix) => {
            const content = `${prefix}${sensitiveWord}${suffix}`;
            const result = await service.filterContent(content);
            
            // 包含敏感词的消息应该被过滤（替换为星号）或拒绝
            if (result.allowed) {
              // 如果允许，敏感词应该被替换
              expect(result.filtered).toBeDefined();
              expect(result.filtered).not.toContain(sensitiveWord);
              expect(result.filtered).toContain('*'.repeat(sensitiveWord.length));
            }
            // 如果不允许，reason应该有值
            if (!result.allowed) {
              expect(result.reason).toBeDefined();
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何空或只包含空白字符的消息，应该被拒绝
     * 验证需求 4.3: 消息内容验证
     */
    it('对于任何空或只包含空白字符的消息，应该被拒绝', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成空白字符串
          fc.constantFrom('', ' ', '  ', '\t', '\n', '   \t\n   '),
          async (content) => {
            const result = await service.filterContent(content);
            
            // 空或空白消息应该被拒绝
            expect(result.allowed).toBe(false);
            expect(result.reason).toBeDefined();
          }
        ),
        { numRuns: 50 }
      );
    }, 30000);

    /**
     * 属性: 对于任何超过长度限制的消息，应该被拒绝
     * 验证需求 4.3: 消息长度验证
     */
    it('对于任何超过长度限制的消息，应该被拒绝', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成超长消息
          fc.integer({ min: 1001, max: 2000 }),
          async (length) => {
            const content = 'a'.repeat(length);
            const result = await service.filterContent(content);
            
            // 超长消息应该被拒绝
            expect(result.allowed).toBe(false);
            expect(result.reason).toContain('过长');
          }
        ),
        { numRuns: 50 }
      );
    }, 30000);

    /**
     * 属性: 对于任何用户，在频率限制窗口内发送超过限制数量的消息应该被拒绝
     * 验证需求 4.4: 玩家发送频率过高时系统应实施频率限制并返回限制提示
     */
    it('对于任何用户，在频率限制窗口内发送超过限制数量的消息应该被拒绝', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成用户ID
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成发送次数（超过限制）
          fc.integer({ min: 11, max: 20 }),
          async (userId, sendCount) => {
            // 清理之前的数据
            memoryStore.clear();

            const results: boolean[] = [];
            
            // 发送多条消息
            for (let i = 0; i < sendCount; i++) {
              const result = await service.checkRateLimit(userId.trim());
              results.push(result);
            }

            // 前10条应该被允许
            const allowedCount = results.filter(r => r).length;
            expect(allowedCount).toBe(10);

            // 超过10条的应该被拒绝
            const rejectedCount = results.filter(r => !r).length;
            expect(rejectedCount).toBe(sendCount - 10);
          }
        ),
        { numRuns: 50 }
      );
    }, 60000);

    /**
     * 属性: 对于任何用户，在频率限制内的消息应该被允许
     * 验证需求 4.4: 正常频率的消息应该被允许
     */
    it('对于任何用户，在频率限制内的消息应该被允许', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成用户ID
          fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          // 生成发送次数（在限制内）
          fc.integer({ min: 1, max: 10 }),
          async (userId, sendCount) => {
            // 清理之前的数据
            memoryStore.clear();

            // 发送消息
            for (let i = 0; i < sendCount; i++) {
              const result = await service.checkRateLimit(userId.trim());
              expect(result).toBe(true);
            }
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);

    /**
     * 属性: 对于任何包含过多重复字符的消息，应该被拒绝
     * 验证需求 4.3: 防刷屏检测
     */
    it('对于任何包含过多重复字符的消息，应该被拒绝', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成单个字符
          fc.constantFrom('a', 'b', 'c', 'd', 'e', '1', '2', '3'),
          // 生成重复次数（超过5次）
          fc.integer({ min: 5, max: 20 }),
          async (char: string, repeatCount: number) => {
            const content = char.repeat(repeatCount);
            const result = await service.filterContent(content);
            
            // 包含过多重复字符的消息应该被拒绝
            expect(result.allowed).toBe(false);
            expect(result.reason).toContain('重复');
          }
        ),
        { numRuns: 50 }
      );
    }, 30000);

    /**
     * 属性: 综合验证 - 对于任何消息，过滤结果应该是确定性的
     * 验证需求 4.3, 4.4: 过滤行为的一致性
     */
    it('对于任何消息，过滤结果应该是确定性的', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成任意消息
          fc.string({ minLength: 0, maxLength: 500 }),
          async (content) => {
            // 对同一消息进行两次过滤
            const result1 = await service.filterContent(content);
            const result2 = await service.filterContent(content);
            
            // 结果应该一致
            expect(result1.allowed).toBe(result2.allowed);
            expect(result1.reason).toBe(result2.reason);
            expect(result1.filtered).toBe(result2.filtered);
          }
        ),
        { numRuns: 100 }
      );
    }, 60000);
  });

  describe('validateMessage - 综合消息验证', () => {
    it('应该对有效消息返回允许', async () => {
      const result = await service.validateMessage(
        'user-123',
        'Hello, world!',
        'Novice Village',
        'novice_village'
      );
      expect(result.allowed).toBe(true);
    });

    it('应该对无效内容返回拒绝', async () => {
      const result = await service.validateMessage(
        'user-123',
        '',
        'Novice Village',
        'novice_village'
      );
      expect(result.allowed).toBe(false);
    });

    it('应该对位置不匹配返回拒绝', async () => {
      const result = await service.validateMessage(
        'user-123',
        'Hello',
        'Tavern',
        'novice_village'
      );
      expect(result.allowed).toBe(false);
    });
  });

  describe('logViolation - 违规记录', () => {
    it('应该成功记录违规行为', async () => {
      await service.logViolation('user-123', ViolationType.CONTENT, {
        reason: 'test violation',
      });

      // 验证Redis被调用
      expect(mockRedisService.setex).toHaveBeenCalled();
    });
  });

  describe('resetUserRateLimit - 重置频率限制', () => {
    it('应该成功重置用户频率限制', async () => {
      // 先发送一些消息
      await service.checkRateLimit('user-123');
      await service.checkRateLimit('user-123');
      
      // 重置
      await service.resetUserRateLimit('user-123');
      
      // 验证Redis del被调用
      expect(mockRedisService.del).toHaveBeenCalled();
    });
  });

  describe('敏感词管理', () => {
    it('应该能够添加敏感词', () => {
      const initialCount = service.getSensitiveWords().length;
      service.addSensitiveWord('测试词', 'replace', 'test');
      expect(service.getSensitiveWords().length).toBe(initialCount + 1);
    });

    it('应该能够移除敏感词', () => {
      service.addSensitiveWord('临时词', 'replace');
      const result = service.removeSensitiveWord('临时词');
      expect(result).toBe(true);
    });

    it('应该返回过滤服务统计信息', () => {
      const stats = service.getFilterStats();
      expect(stats.sensitiveWordsCount).toBeGreaterThan(0);
      expect(stats.rateLimit).toBe(10);
      expect(stats.maxMessageLength).toBe(1000);
    });
  });
});
