/**
 * RealRedisService单元测试
 * 
 * 功能描述：
 * - 测试真实Redis服务的所有公共方法
 * - 验证Redis连接管理和错误处理
 * - 测试正常情况、异常情况和边界情况
 * - 使用Mock Redis客户端进行隔离测试
 * 
 * 职责分离：
 * - 单元测试：隔离测试每个方法的功能
 * - Mock测试：使用模拟Redis客户端避免真实连接
 * - 异常测试：验证错误处理机制
 * - 边界测试：测试参数边界和特殊情况
 * 
 * 最近修改：
 * - 2025-01-07: 功能新增 - 创建RealRedisService完整单元测试，覆盖所有公共方法
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2025-01-07
 * @lastModified 2025-01-07
 */
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';
import { RealRedisService } from './real_redis.service';
import Redis from 'ioredis';

// Mock ioredis
jest.mock('ioredis');

describe('RealRedisService', () => {
  let service: RealRedisService;
  let mockRedis: jest.Mocked<Redis>;
  let mockConfigService: jest.Mocked<ConfigService>;

  beforeEach(async () => {
    // 创建Mock Redis实例
    mockRedis = {
      set: jest.fn(),
      get: jest.fn(),
      del: jest.fn(),
      exists: jest.fn(),
      expire: jest.fn(),
      ttl: jest.fn(),
      flushall: jest.fn(),
      setex: jest.fn(),
      incr: jest.fn(),
      sadd: jest.fn(),
      srem: jest.fn(),
      smembers: jest.fn(),
      disconnect: jest.fn(),
      on: jest.fn(),
    } as any;

    // Mock Redis构造函数
    (Redis as jest.MockedClass<typeof Redis>).mockImplementation(() => mockRedis);

    // 创建Mock ConfigService
    mockConfigService = {
      get: jest.fn(),
    } as any;

    mockConfigService.get.mockImplementation((key: string, defaultValue?: any) => {
      const config = {
        'REDIS_HOST': 'localhost',
        'REDIS_PORT': 6379,
        'REDIS_PASSWORD': undefined,
        'REDIS_DB': 0,
      };
      return config[key] || defaultValue;
    });

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        RealRedisService,
        { provide: ConfigService, useValue: mockConfigService },
      ],
    }).compile();

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

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

  describe('构造函数和初始化', () => {
    it('should create service successfully', () => {
      expect(service).toBeDefined();
    });

    it('should initialize Redis with correct config', () => {
      expect(Redis).toHaveBeenCalledWith({
        host: 'localhost',
        port: 6379,
        password: undefined,
        db: 0,
        retryDelayOnFailover: 100,
        maxRetriesPerRequest: 3,
        lazyConnect: true,
      });
    });

    it('should setup Redis event listeners', () => {
      expect(mockRedis.on).toHaveBeenCalledWith('connect', expect.any(Function));
      expect(mockRedis.on).toHaveBeenCalledWith('error', expect.any(Function));
      expect(mockRedis.on).toHaveBeenCalledWith('close', expect.any(Function));
    });
  });

  describe('set', () => {
    it('should set key-value without TTL', async () => {
      mockRedis.set.mockResolvedValue('OK');

      await service.set('testKey', 'testValue');

      expect(mockRedis.set).toHaveBeenCalledWith('testKey', 'testValue');
      expect(mockRedis.setex).not.toHaveBeenCalled();
    });

    it('should set key-value with TTL', async () => {
      mockRedis.setex.mockResolvedValue('OK');

      await service.set('testKey', 'testValue', 3600);

      expect(mockRedis.setex).toHaveBeenCalledWith('testKey', 3600, 'testValue');
      expect(mockRedis.set).not.toHaveBeenCalled();
    });

    it('should not set TTL when TTL is 0', async () => {
      mockRedis.set.mockResolvedValue('OK');

      await service.set('testKey', 'testValue', 0);

      expect(mockRedis.set).toHaveBeenCalledWith('testKey', 'testValue');
      expect(mockRedis.setex).not.toHaveBeenCalled();
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.set.mockRejectedValue(error);

      await expect(service.set('testKey', 'testValue')).rejects.toThrow(error);
    });
  });

  describe('get', () => {
    it('should return value when key exists', async () => {
      mockRedis.get.mockResolvedValue('testValue');

      const result = await service.get('testKey');

      expect(result).toBe('testValue');
      expect(mockRedis.get).toHaveBeenCalledWith('testKey');
    });

    it('should return null when key does not exist', async () => {
      mockRedis.get.mockResolvedValue(null);

      const result = await service.get('nonExistentKey');

      expect(result).toBeNull();
      expect(mockRedis.get).toHaveBeenCalledWith('nonExistentKey');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.get.mockRejectedValue(error);

      await expect(service.get('testKey')).rejects.toThrow(error);
    });
  });

  describe('del', () => {
    it('should return true when key is deleted', async () => {
      mockRedis.del.mockResolvedValue(1);

      const result = await service.del('testKey');

      expect(result).toBe(true);
      expect(mockRedis.del).toHaveBeenCalledWith('testKey');
    });

    it('should return false when key does not exist', async () => {
      mockRedis.del.mockResolvedValue(0);

      const result = await service.del('nonExistentKey');

      expect(result).toBe(false);
      expect(mockRedis.del).toHaveBeenCalledWith('nonExistentKey');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.del.mockRejectedValue(error);

      await expect(service.del('testKey')).rejects.toThrow(error);
    });
  });

  describe('exists', () => {
    it('should return true when key exists', async () => {
      mockRedis.exists.mockResolvedValue(1);

      const result = await service.exists('testKey');

      expect(result).toBe(true);
      expect(mockRedis.exists).toHaveBeenCalledWith('testKey');
    });

    it('should return false when key does not exist', async () => {
      mockRedis.exists.mockResolvedValue(0);

      const result = await service.exists('nonExistentKey');

      expect(result).toBe(false);
      expect(mockRedis.exists).toHaveBeenCalledWith('nonExistentKey');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.exists.mockRejectedValue(error);

      await expect(service.exists('testKey')).rejects.toThrow(error);
    });
  });

  describe('expire', () => {
    it('should set expiration time successfully', async () => {
      mockRedis.expire.mockResolvedValue(1);

      await service.expire('testKey', 3600);

      expect(mockRedis.expire).toHaveBeenCalledWith('testKey', 3600);
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.expire.mockRejectedValue(error);

      await expect(service.expire('testKey', 3600)).rejects.toThrow(error);
    });
  });

  describe('ttl', () => {
    it('should return remaining TTL', async () => {
      mockRedis.ttl.mockResolvedValue(3600);

      const result = await service.ttl('testKey');

      expect(result).toBe(3600);
      expect(mockRedis.ttl).toHaveBeenCalledWith('testKey');
    });

    it('should return -1 for keys without expiration', async () => {
      mockRedis.ttl.mockResolvedValue(-1);

      const result = await service.ttl('testKey');

      expect(result).toBe(-1);
    });

    it('should return -2 for non-existent keys', async () => {
      mockRedis.ttl.mockResolvedValue(-2);

      const result = await service.ttl('nonExistentKey');

      expect(result).toBe(-2);
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.ttl.mockRejectedValue(error);

      await expect(service.ttl('testKey')).rejects.toThrow(error);
    });
  });

  describe('flushall', () => {
    it('should clear all data successfully', async () => {
      mockRedis.flushall.mockResolvedValue('OK');

      await service.flushall();

      expect(mockRedis.flushall).toHaveBeenCalled();
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.flushall.mockRejectedValue(error);

      await expect(service.flushall()).rejects.toThrow(error);
    });
  });

  describe('setex', () => {
    it('should set key with expiration time', async () => {
      mockRedis.setex.mockResolvedValue('OK');

      await service.setex('testKey', 1800, 'testValue');

      expect(mockRedis.setex).toHaveBeenCalledWith('testKey', 1800, 'testValue');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.setex.mockRejectedValue(error);

      await expect(service.setex('testKey', 1800, 'testValue')).rejects.toThrow(error);
    });
  });

  describe('incr', () => {
    it('should increment existing numeric value', async () => {
      mockRedis.incr.mockResolvedValue(6);

      const result = await service.incr('counter');

      expect(result).toBe(6);
      expect(mockRedis.incr).toHaveBeenCalledWith('counter');
    });

    it('should initialize non-existent key to 1', async () => {
      mockRedis.incr.mockResolvedValue(1);

      const result = await service.incr('newCounter');

      expect(result).toBe(1);
      expect(mockRedis.incr).toHaveBeenCalledWith('newCounter');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.incr.mockRejectedValue(error);

      await expect(service.incr('counter')).rejects.toThrow(error);
    });
  });

  describe('sadd', () => {
    it('should add member to set successfully', async () => {
      mockRedis.sadd.mockResolvedValue(1);

      await service.sadd('users', 'user123');

      expect(mockRedis.sadd).toHaveBeenCalledWith('users', 'user123');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.sadd.mockRejectedValue(error);

      await expect(service.sadd('users', 'user123')).rejects.toThrow(error);
    });
  });

  describe('srem', () => {
    it('should remove member from set successfully', async () => {
      mockRedis.srem.mockResolvedValue(1);

      await service.srem('users', 'user123');

      expect(mockRedis.srem).toHaveBeenCalledWith('users', 'user123');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.srem.mockRejectedValue(error);

      await expect(service.srem('users', 'user123')).rejects.toThrow(error);
    });
  });

  describe('smembers', () => {
    it('should return all set members', async () => {
      const members = ['user1', 'user2', 'user3'];
      mockRedis.smembers.mockResolvedValue(members);

      const result = await service.smembers('users');

      expect(result).toEqual(members);
      expect(mockRedis.smembers).toHaveBeenCalledWith('users');
    });

    it('should return empty array for non-existent set', async () => {
      mockRedis.smembers.mockResolvedValue([]);

      const result = await service.smembers('nonExistentSet');

      expect(result).toEqual([]);
      expect(mockRedis.smembers).toHaveBeenCalledWith('nonExistentSet');
    });

    it('should throw error when Redis operation fails', async () => {
      const error = new Error('Redis connection failed');
      mockRedis.smembers.mockRejectedValue(error);

      await expect(service.smembers('users')).rejects.toThrow(error);
    });
  });

  describe('onModuleDestroy', () => {
    it('should disconnect Redis when module is destroyed', () => {
      service.onModuleDestroy();

      expect(mockRedis.disconnect).toHaveBeenCalled();
    });

    it('should handle case when Redis is not initialized', () => {
      // 创建一个没有Redis实例的服务
      const serviceWithoutRedis = Object.create(RealRedisService.prototype);
      
      expect(() => serviceWithoutRedis.onModuleDestroy()).not.toThrow();
    });
  });

  describe('边界情况测试', () => {
    it('should handle empty string key', async () => {
      mockRedis.set.mockResolvedValue('OK');

      await service.set('', 'value');

      expect(mockRedis.set).toHaveBeenCalledWith('', 'value');
    });

    it('should handle empty string value', async () => {
      mockRedis.set.mockResolvedValue('OK');

      await service.set('key', '');

      expect(mockRedis.set).toHaveBeenCalledWith('key', '');
    });

    it('should handle very large TTL values', async () => {
      mockRedis.setex.mockResolvedValue('OK');

      await service.set('key', 'value', 2147483647); // Max 32-bit integer

      expect(mockRedis.setex).toHaveBeenCalledWith('key', 2147483647, 'value');
    });

    it('should handle negative TTL values', async () => {
      mockRedis.set.mockResolvedValue('OK');

      await service.set('key', 'value', -1);

      expect(mockRedis.set).toHaveBeenCalledWith('key', 'value');
      expect(mockRedis.setex).not.toHaveBeenCalled();
    });
  });
});