/**
 * 会话清理定时任务服务测试
 * 
 * 功能描述：
 * - 测试SessionCleanupService的核心功能
 * - 包含属性测试验证定时清理机制
 * - 包含属性测试验证资源释放完整性
 * 
 * **Feature: zulip-integration, Property 13: 定时清理机制**
 * **Validates: Requirements 6.1, 6.2, 6.3**
 * 
 * **Feature: zulip-integration, Property 14: 资源释放完整性**
 * **Validates: Requirements 6.4, 6.5**
 * 
 * @author angjustinl
 * @version 1.0.0
 * @since 2025-12-31
 */

import { Test, TestingModule } from '@nestjs/testing';
import * as fc from 'fast-check';
import { 
  SessionCleanupService, 
  CleanupConfig, 
  CleanupResult 
} from './session_cleanup.service';
import { SessionManagerService } from './session_manager.service';
import { IZulipClientPoolService } from '../../../core/zulip_core/zulip_core.interfaces';

describe('SessionCleanupService', () => {
  let service: SessionCleanupService;
  let mockSessionManager: jest.Mocked<SessionManagerService>;
  let mockZulipClientPool: jest.Mocked<IZulipClientPoolService>;

  // 模拟清理结果
  const createMockCleanupResult = (overrides: Partial<any> = {}): any => ({
    cleanedCount: 3,
    zulipQueueIds: ['queue-1', 'queue-2', 'queue-3'],
    duration: 150,
    timestamp: new Date(),
    ...overrides,
  });

  beforeEach(async () => {
    jest.clearAllMocks();
    jest.clearAllTimers();
    // 确保每个测试开始时都使用真实定时器
    jest.useRealTimers();

    mockSessionManager = {
      cleanupExpiredSessions: jest.fn(),
      getSession: jest.fn(),
      destroySession: jest.fn(),
      createSession: jest.fn(),
      updatePlayerPosition: jest.fn(),
      getSocketsInMap: jest.fn(),
      injectContext: jest.fn(),
    } as any;

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

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        SessionCleanupService,
        {
          provide: SessionManagerService,
          useValue: mockSessionManager,
        },
        {
          provide: 'ZULIP_CLIENT_POOL_SERVICE',
          useValue: mockZulipClientPool,
        },
      ],
    }).compile();

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

  afterEach(async () => {
    // 确保停止所有清理任务
    service.stopCleanupTask();
    
    // 等待任何正在进行的异步操作完成
    await new Promise(resolve => setImmediate(resolve));
    
    // 清理定时器
    jest.clearAllTimers();
    
    // 恢复真实定时器
    jest.useRealTimers();
  });

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

  describe('startCleanupTask - 启动清理任务', () => {
    it('应该启动定时清理任务', () => {
      service.startCleanupTask();

      const status = service.getStatus();
      expect(status.isEnabled).toBe(true);
    });

    it('应该在已启动时不重复启动', () => {
      service.startCleanupTask();
      service.startCleanupTask(); // 第二次调用

      const status = service.getStatus();
      expect(status.isEnabled).toBe(true);
    });

    it('应该立即执行一次清理', async () => {
      jest.useFakeTimers();
      
      mockSessionManager.cleanupExpiredSessions.mockResolvedValue(
        createMockCleanupResult({ cleanedCount: 2 })
      );

      service.startCleanupTask();

      // 等待立即执行的清理完成
      await jest.runOnlyPendingTimersAsync();

      expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
      
      // 确保清理任务被停止
      service.stopCleanupTask();
      jest.useRealTimers();
    });
  });

  describe('stopCleanupTask - 停止清理任务', () => {
    it('应该停止定时清理任务', () => {
      service.startCleanupTask();
      service.stopCleanupTask();

      const status = service.getStatus();
      expect(status.isEnabled).toBe(false);
    });

    it('应该在未启动时安全停止', () => {
      service.stopCleanupTask();

      const status = service.getStatus();
      expect(status.isEnabled).toBe(false);
    });
  });

  describe('runCleanup - 执行清理', () => {
    it('应该成功执行清理并返回结果', async () => {
      const mockResult = createMockCleanupResult({
        cleanedCount: 5,
        zulipQueueIds: ['queue-1', 'queue-2', 'queue-3', 'queue-4', 'queue-5'],
      });

      mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);

      const result = await service.runCleanup();

      expect(result.success).toBe(true);
      expect(result.cleanedSessions).toBe(5);
      expect(result.deregisteredQueues).toBe(5);
      expect(result.duration).toBeGreaterThanOrEqual(0); // 修改为 >= 0，因为测试环境可能很快
      expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
    });

    it('应该处理清理过程中的错误', async () => {
      const error = new Error('清理失败');
      mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);

      const result = await service.runCleanup();

      expect(result.success).toBe(false);
      expect(result.error).toBe('清理失败');
      expect(result.cleanedSessions).toBe(0);
      expect(result.deregisteredQueues).toBe(0);
    });

    it('应该防止并发执行', async () => {
      let resolveFirst: () => void;
      const firstPromise = new Promise<any>(resolve => {
        resolveFirst = () => resolve(createMockCleanupResult());
      });

      mockSessionManager.cleanupExpiredSessions.mockReturnValueOnce(firstPromise);

      // 同时启动两个清理任务
      const promise1 = service.runCleanup();
      const promise2 = service.runCleanup();

      // 第二个应该立即返回失败
      const result2 = await promise2;
      expect(result2.success).toBe(false);
      expect(result2.error).toContain('正在执行中');

      // 完成第一个任务
      resolveFirst!();
      const result1 = await promise1;
      expect(result1.success).toBe(true);
    }, 15000);

    it('应该记录最后一次清理结果', async () => {
      const mockResult = createMockCleanupResult({ cleanedCount: 3 });
      mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);

      await service.runCleanup();

      const lastResult = service.getLastCleanupResult();
      expect(lastResult).not.toBeNull();
      expect(lastResult!.cleanedSessions).toBe(3);
      expect(lastResult!.success).toBe(true);
    });
  });

  describe('getStatus - 获取状态', () => {
    it('应该返回正确的状态信息', () => {
      const status = service.getStatus();

      expect(status).toHaveProperty('isRunning');
      expect(status).toHaveProperty('isEnabled');
      expect(status).toHaveProperty('config');
      expect(status).toHaveProperty('lastResult');
      expect(typeof status.isRunning).toBe('boolean');
      expect(typeof status.isEnabled).toBe('boolean');
    });

    it('应该反映任务启动状态', () => {
      let status = service.getStatus();
      expect(status.isEnabled).toBe(false);

      service.startCleanupTask();
      status = service.getStatus();
      expect(status.isEnabled).toBe(true);

      service.stopCleanupTask();
      status = service.getStatus();
      expect(status.isEnabled).toBe(false);
    });
  });

  describe('updateConfig - 更新配置', () => {
    it('应该更新清理配置', () => {
      const newConfig: Partial<CleanupConfig> = {
        intervalMs: 10 * 60 * 1000, // 10分钟
        sessionTimeoutMinutes: 60,   // 60分钟
      };

      service.updateConfig(newConfig);

      const status = service.getStatus();
      expect(status.config.intervalMs).toBe(10 * 60 * 1000);
      expect(status.config.sessionTimeoutMinutes).toBe(60);
    });

    it('应该在配置更改后重启任务', () => {
      service.startCleanupTask();
      
      const newConfig: Partial<CleanupConfig> = {
        intervalMs: 2 * 60 * 1000, // 2分钟
      };

      service.updateConfig(newConfig);

      const status = service.getStatus();
      expect(status.isEnabled).toBe(true);
      expect(status.config.intervalMs).toBe(2 * 60 * 1000);
    });

    it('应该支持禁用清理任务', () => {
      service.startCleanupTask();
      
      service.updateConfig({ enabled: false });

      const status = service.getStatus();
      expect(status.isEnabled).toBe(false);
    });
  });
  /**
   * 属性测试: 定时清理机制
   * 
   * **Feature: zulip-integration, Property 13: 定时清理机制**
   * **Validates: Requirements 6.1, 6.2, 6.3**
   * 
   * 系统应该定期清理过期的游戏会话，释放相关资源，
   * 并确保清理过程不影响正常的游戏服务
   */
  describe('Property 13: 定时清理机制', () => {
    /**
     * 属性: 对于任何有效的清理配置，系统应该按配置间隔执行清理
     * 验证需求 6.1: 系统应定期检查并清理过期的游戏会话
     */
    it('对于任何有效的清理配置，系统应该按配置间隔执行清理', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成有效的清理间隔（1-5分钟，减少范围）
          fc.integer({ min: 1, max: 5 }).map(minutes => minutes * 60 * 1000),
          // 生成有效的会话超时时间（10-60分钟，减少范围）
          fc.integer({ min: 10, max: 60 }),
          async (intervalMs, sessionTimeoutMinutes) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            jest.useFakeTimers();
            
            try {
              const config: Partial<CleanupConfig> = {
                intervalMs,
                sessionTimeoutMinutes,
                enabled: true,
              };

              // 模拟清理结果
              mockSessionManager.cleanupExpiredSessions.mockResolvedValue(
                createMockCleanupResult({ cleanedCount: 2 })
              );

              service.updateConfig(config);
              service.startCleanupTask();

              // 验证配置被正确设置
              const status = service.getStatus();
              expect(status.config.intervalMs).toBe(intervalMs);
              expect(status.config.sessionTimeoutMinutes).toBe(sessionTimeoutMinutes);
              expect(status.isEnabled).toBe(true);

              // 验证立即执行了一次清理
              await jest.runOnlyPendingTimersAsync();
              expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(sessionTimeoutMinutes);

            } finally {
              service.stopCleanupTask();
              jest.useRealTimers();
            }
          }
        ),
        { numRuns: 20, timeout: 5000 } // 减少运行次数并添加超时
      );
    }, 15000);

    /**
     * 属性: 对于任何清理操作，都应该记录清理结果和统计信息
     * 验证需求 6.2: 清理过程中系统应记录清理的会话数量和释放的资源
     */
    it('对于任何清理操作，都应该记录清理结果和统计信息', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成清理的会话数量
          fc.integer({ min: 0, max: 10 }),
          // 生成Zulip队列ID列表
          fc.array(
            fc.string({ minLength: 5, maxLength: 15 }).filter(s => s.trim().length > 0),
            { minLength: 0, maxLength: 10 }
          ),
          async (cleanedCount, queueIds) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            
            const mockResult = createMockCleanupResult({
              cleanedCount,
              zulipQueueIds: queueIds.slice(0, cleanedCount), // 确保队列数量不超过清理数量
            });

            mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);

            const result = await service.runCleanup();

            // 验证清理结果被正确记录
            expect(result.success).toBe(true);
            expect(result.cleanedSessions).toBe(cleanedCount);
            expect(result.deregisteredQueues).toBe(Math.min(queueIds.length, cleanedCount));
            expect(result.duration).toBeGreaterThanOrEqual(0);
            expect(result.timestamp).toBeInstanceOf(Date);

            // 验证最后一次清理结果被保存
            const lastResult = service.getLastCleanupResult();
            expect(lastResult).not.toBeNull();
            expect(lastResult!.cleanedSessions).toBe(cleanedCount);
          }
        ),
        { numRuns: 20, timeout: 3000 } // 减少运行次数并添加超时
      );
    }, 10000);

    /**
     * 属性: 清理过程中发生错误时，系统应该正确处理并记录错误信息
     * 验证需求 6.3: 清理过程中出现错误时系统应记录错误信息并继续正常服务
     */
    it('清理过程中发生错误时，系统应该正确处理并记录错误信息', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成各种错误消息
          fc.string({ minLength: 5, maxLength: 50 }).filter(s => s.trim().length > 0),
          async (errorMessage) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            
            const error = new Error(errorMessage.trim());
            mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);

            const result = await service.runCleanup();

            // 验证错误被正确处理
            expect(result.success).toBe(false);
            expect(result.error).toBe(errorMessage.trim());
            expect(result.cleanedSessions).toBe(0);
            expect(result.deregisteredQueues).toBe(0);
            expect(result.duration).toBeGreaterThanOrEqual(0);

            // 验证错误结果被保存
            const lastResult = service.getLastCleanupResult();
            expect(lastResult).not.toBeNull();
            expect(lastResult!.success).toBe(false);
            expect(lastResult!.error).toBe(errorMessage.trim());
          }
        ),
        { numRuns: 20, timeout: 3000 } // 减少运行次数并添加超时
      );
    }, 10000);

    /**
     * 属性: 并发清理请求应该被正确处理，避免重复执行
     * 验证需求 6.1: 系统应避免同时执行多个清理任务
     */
    it('并发清理请求应该被正确处理，避免重复执行', async () => {
      // 重置mock
      jest.clearAllMocks();
      
      // 创建一个可控的Promise，使用实际的异步行为
      let resolveCleanup: (value: any) => void;
      const cleanupPromise = new Promise<any>(resolve => {
        resolveCleanup = resolve;
      });

      mockSessionManager.cleanupExpiredSessions.mockReturnValue(cleanupPromise);

      // 启动第一个清理请求（应该成功）
      const promise1 = service.runCleanup();
      
      // 等待一个微任务周期，确保第一个请求开始执行
      await Promise.resolve();
      
      // 启动第二个和第三个清理请求（应该被拒绝）
      const promise2 = service.runCleanup();
      const promise3 = service.runCleanup();
      
      // 第二个和第三个请求应该立即返回失败
      const result2 = await promise2;
      const result3 = await promise3;
      
      expect(result2.success).toBe(false);
      expect(result2.error).toContain('正在执行中');
      expect(result3.success).toBe(false);
      expect(result3.error).toContain('正在执行中');
      
      // 完成第一个清理操作
      resolveCleanup!(createMockCleanupResult({ cleanedCount: 1 }));
      const result1 = await promise1;
      
      expect(result1.success).toBe(true);
    }, 10000);
  });
  /**
   * 属性测试: 资源释放完整性
   * 
   * **Feature: zulip-integration, Property 14: 资源释放完整性**
   * **Validates: Requirements 6.4, 6.5**
   * 
   * 清理过期会话时，系统应该完整释放所有相关资源，
   * 包括Zulip事件队列、内存缓存等，确保不会造成资源泄漏
   */
  describe('Property 14: 资源释放完整性', () => {
    /**
     * 属性: 对于任何过期会话，清理时应该释放所有相关的Zulip资源
     * 验证需求 6.4: 清理会话时系统应注销对应的Zulip事件队列
     */
    it('对于任何过期会话，清理时应该释放所有相关的Zulip资源', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成过期会话数量
          fc.integer({ min: 1, max: 5 }),
          // 生成每个会话对应的Zulip队列ID
          fc.array(
            fc.string({ minLength: 8, maxLength: 15 }).filter(s => s.trim().length > 0),
            { minLength: 1, maxLength: 5 }
          ),
          async (sessionCount, queueIds) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            
            const actualQueueIds = queueIds.slice(0, sessionCount);
            const mockResult = createMockCleanupResult({
              cleanedCount: sessionCount,
              zulipQueueIds: actualQueueIds,
            });

            mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);

            const result = await service.runCleanup();

            // 验证清理成功
            expect(result.success).toBe(true);
            expect(result.cleanedSessions).toBe(sessionCount);
            
            // 验证Zulip队列被处理（这里简化为计数验证）
            expect(result.deregisteredQueues).toBe(actualQueueIds.length);
            
            // 验证SessionManager被调用清理过期会话
            expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
          }
        ),
        { numRuns: 20, timeout: 3000 } // 减少运行次数并添加超时
      );
    }, 10000);

    /**
     * 属性: 清理操作应该是原子性的，要么全部成功要么全部回滚
     * 验证需求 6.5: 清理过程应确保数据一致性，避免部分清理导致的不一致状态
     */
    it('清理操作应该是原子性的，要么全部成功要么全部回滚', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成是否模拟清理失败
          fc.boolean(),
          // 生成会话数量
          fc.integer({ min: 1, max: 3 }),
          async (shouldFail, sessionCount) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            
            if (shouldFail) {
              // 模拟清理失败
              const error = new Error('清理操作失败');
              mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);
            } else {
              // 模拟清理成功
              const mockResult = createMockCleanupResult({
                cleanedCount: sessionCount,
                zulipQueueIds: Array.from({ length: sessionCount }, (_, i) => `queue-${i}`),
              });
              mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
            }

            const result = await service.runCleanup();

            if (shouldFail) {
              // 失败时应该没有任何资源被释放
              expect(result.success).toBe(false);
              expect(result.cleanedSessions).toBe(0);
              expect(result.deregisteredQueues).toBe(0);
              expect(result.error).toBeDefined();
            } else {
              // 成功时所有资源都应该被正确处理
              expect(result.success).toBe(true);
              expect(result.cleanedSessions).toBe(sessionCount);
              expect(result.deregisteredQueues).toBe(sessionCount);
              expect(result.error).toBeUndefined();
            }

            // 验证结果的一致性
            expect(result.timestamp).toBeInstanceOf(Date);
            expect(result.duration).toBeGreaterThanOrEqual(0);
          }
        ),
        { numRuns: 20, timeout: 3000 } // 减少运行次数并添加超时
      );
    }, 10000);

    /**
     * 属性: 清理配置更新应该正确重启清理任务而不丢失状态
     * 验证需求 6.5: 配置更新时系统应保持服务连续性
     */
    it('清理配置更新应该正确重启清理任务而不丢失状态', async () => {
      await fc.assert(
        fc.asyncProperty(
          // 生成初始配置
          fc.record({
            intervalMs: fc.integer({ min: 1, max: 3 }).map(m => m * 60 * 1000),
            sessionTimeoutMinutes: fc.integer({ min: 10, max: 30 }),
          }),
          // 生成新配置
          fc.record({
            intervalMs: fc.integer({ min: 1, max: 3 }).map(m => m * 60 * 1000),
            sessionTimeoutMinutes: fc.integer({ min: 10, max: 30 }),
          }),
          async (initialConfig, newConfig) => {
            // 重置mock以确保每次测试都是干净的状态
            jest.clearAllMocks();
            
            try {
              // 设置初始配置并启动任务
              service.updateConfig(initialConfig);
              service.startCleanupTask();

              let status = service.getStatus();
              expect(status.isEnabled).toBe(true);
              expect(status.config.intervalMs).toBe(initialConfig.intervalMs);

              // 更新配置
              service.updateConfig(newConfig);

              // 验证配置更新后任务仍在运行
              status = service.getStatus();
              expect(status.isEnabled).toBe(true);
              expect(status.config.intervalMs).toBe(newConfig.intervalMs);
              expect(status.config.sessionTimeoutMinutes).toBe(newConfig.sessionTimeoutMinutes);

            } finally {
              service.stopCleanupTask();
            }
          }
        ),
        { numRuns: 15, timeout: 3000 } // 减少运行次数并添加超时
      );
    }, 10000);
  });

  describe('模块生命周期', () => {
    it('应该在模块初始化时启动清理任务', async () => {
      // 重新创建服务实例来测试模块初始化
      const module: TestingModule = await Test.createTestingModule({
        providers: [
          SessionCleanupService,
          {
            provide: SessionManagerService,
            useValue: mockSessionManager,
          },
          {
            provide: 'ZULIP_CLIENT_POOL_SERVICE',
            useValue: mockZulipClientPool,
          },
        ],
      }).compile();

      const newService = module.get<SessionCleanupService>(SessionCleanupService);
      
      // 模拟模块初始化
      await newService.onModuleInit();

      const status = newService.getStatus();
      expect(status.isEnabled).toBe(true);

      // 清理
      await newService.onModuleDestroy();
    });

    it('应该在模块销毁时停止清理任务', async () => {
      service.startCleanupTask();
      
      await service.onModuleDestroy();

      const status = service.getStatus();
      expect(status.isEnabled).toBe(false);
    });
  });
});