/**
 * Zulip账号关联服务测试
 * 
 * 功能描述：
 * - 根据环境配置自动选择测试模式（数据库 vs Mock）
 * - 在配置数据库时测试真实数据库操作
 * - 在未配置数据库时使用Mock测试业务逻辑
 * - 测试CRUD操作和业务逻辑
 * - 测试异常处理和边界情况
 * 
 * @author angjustinl
 * @version 1.1.0
 * @since 2025-01-07
 * @lastModified 2026-01-10
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { ZulipAccountsService } from './zulip_accounts.service';
import { ZulipAccountsRepository } from './zulip_accounts.repository';
import { ZulipAccounts } from './zulip_accounts.entity';
import { Users } from '../users/users.entity';
import { CreateZulipAccountDto, UpdateZulipAccountDto } from './zulip_accounts.dto';
import { AppLoggerService } from '../../utils/logger/logger.service';

/**
 * 检查是否配置了数据库
 */
function isDatabaseConfigured(): boolean {
  const requiredEnvVars = ['DB_HOST', 'DB_PORT', 'DB_USERNAME', 'DB_PASSWORD', 'DB_NAME'];
  const hasAllVars = requiredEnvVars.every(varName => process.env[varName]);
  
  // 对于单元测试，我们优先使用Mock模式以确保测试的稳定性和速度
  // 数据库集成测试应该在专门的集成测试文件中进行
  const forceUseDatabase = process.env.FORCE_DATABASE_TESTS === 'true';
  
  return hasAllVars && forceUseDatabase;
}

describe('ZulipAccountsService', () => {
  let service: ZulipAccountsService;
  let repository: jest.Mocked<ZulipAccountsRepository> | ZulipAccountsRepository;
  let module: TestingModule;
  
  const isDbConfigured = isDatabaseConfigured();
  const testMode = isDbConfigured ? 'Database' : 'Mock';

  console.log(`🧪 运行 ZulipAccountsService 测试 - ${testMode} 模式`);
  if (isDbConfigured) {
    console.log('📊 检测到数据库配置，将测试真实数据库操作');
  } else {
    console.log('🎭 未检测到数据库配置，使用Mock测试业务逻辑');
  }

  const mockAccount: ZulipAccounts = {
    id: BigInt(1),
    gameUserId: BigInt(12345),
    zulipUserId: 67890,
    zulipEmail: 'test@example.com',
    zulipFullName: '测试用户',
    zulipApiKeyEncrypted: 'encrypted_api_key',
    status: 'active',
    lastVerifiedAt: new Date(),
    lastSyncedAt: new Date(),
    errorMessage: null,
    retryCount: 0,
    createdAt: new Date(),
    updatedAt: new Date(),
    gameUser: null,
    isActive: () => true,
    isHealthy: () => true,
    canBeDeleted: () => false,
    isStale: () => false,
    needsVerification: () => false,
    shouldRetry: () => false,
    updateVerificationTime: () => {},
    updateSyncTime: () => {},
    setError: () => {},
    clearError: () => {},
    resetRetryCount: () => {},
    activate: () => {},
    suspend: () => {},
    deactivate: () => {},
  };

  beforeEach(async () => {
    if (isDbConfigured) {
      // 数据库模式：使用真实的数据库连接
      module = await Test.createTestingModule({
        imports: [
          ConfigModule.forRoot({
            envFilePath: ['.env.test', '.env'],
            isGlobal: true,
          }),
          TypeOrmModule.forRoot({
            type: 'mysql',
            host: process.env.DB_HOST,
            port: parseInt(process.env.DB_PORT || '3306'),
            username: process.env.DB_USERNAME,
            password: process.env.DB_PASSWORD,
            database: process.env.DB_NAME,
            entities: [ZulipAccounts, Users], // 包含所有相关实体
            synchronize: false, // 生产环境应该为false
            logging: false,
          }),
          TypeOrmModule.forFeature([ZulipAccounts, Users]), // 包含所有相关实体
        ],
        providers: [
          ZulipAccountsService,
          ZulipAccountsRepository,
        ],
      }).compile();

      service = module.get<ZulipAccountsService>(ZulipAccountsService);
      repository = module.get<ZulipAccountsRepository>(ZulipAccountsRepository);
    } else {
      // Mock模式：使用模拟的Repository
      const mockRepository = {
        create: jest.fn(),
        findByGameUserId: jest.fn(),
        findByZulipUserId: jest.fn(),
        findByZulipEmail: jest.fn(),
        findById: jest.fn(),
        update: jest.fn(),
        updateByGameUserId: jest.fn(),
        delete: jest.fn(),
        deleteByGameUserId: jest.fn(),
        findMany: jest.fn(),
        findAccountsNeedingVerification: jest.fn(),
        findErrorAccounts: jest.fn(),
        batchUpdateStatus: jest.fn(),
        getStatusStatistics: jest.fn(),
        existsByEmail: jest.fn(),
        existsByZulipUserId: jest.fn(),
        existsByGameUserId: jest.fn(),
      };

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

      const mockCacheManager = {
        get: jest.fn(),
        set: jest.fn(),
        del: jest.fn(),
        reset: jest.fn(),
      };

      module = await Test.createTestingModule({
        providers: [
          ZulipAccountsService,
          {
            provide: ZulipAccountsRepository,
            useValue: mockRepository,
          },
          {
            provide: AppLoggerService,
            useValue: mockLogger,
          },
          {
            provide: 'CACHE_MANAGER',
            useValue: mockCacheManager,
          },
        ],
      }).compile();

      service = module.get<ZulipAccountsService>(ZulipAccountsService);
      repository = module.get(ZulipAccountsRepository) as jest.Mocked<ZulipAccountsRepository>;
    }
  });

  afterEach(async () => {
    if (module) {
      await module.close();
    }
  });

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

  // 为数据库测试生成唯一的测试数据
  const generateTestData = (suffix: string = Date.now().toString()) => {
    const timestamp = Date.now().toString();
    return {
      gameUserId: isDbConfigured ? `test_${timestamp}_${suffix}` : timestamp.slice(-5), // Mock模式使用纯数字
      zulipUserId: parseInt(`9${timestamp.slice(-5)}`), // 确保是数字且唯一
      zulipEmail: `test_${timestamp}_${suffix}@example.com`,
      zulipFullName: `测试用户_${timestamp}_${suffix}`,
      zulipApiKeyEncrypted: 'encrypted_api_key',
      status: 'active' as const,
    };
  };

  if (isDbConfigured) {
    // 数据库模式测试
    describe('Database Mode Tests', () => {
      describe('create', () => {
        it('should create a new account successfully in database', async () => {
          const createDto = generateTestData('create');
          
          const result = await service.create(createDto);

          expect(result).toBeDefined();
          expect(result.gameUserId).toBe(createDto.gameUserId);
          expect(result.zulipEmail).toBe(createDto.zulipEmail);
          expect(result.zulipUserId).toBe(createDto.zulipUserId);
          expect(result.status).toBe('active');

          // 清理测试数据
          await service.deleteByGameUserId(createDto.gameUserId).catch(() => {});
        });

        it('should throw ConflictException if game user already has account in database', async () => {
          const createDto = generateTestData('conflict');
          
          // 先创建一个账号
          await service.create(createDto);

          // 尝试创建重复的账号
          await expect(service.create(createDto)).rejects.toThrow(ConflictException);

          // 清理测试数据
          await service.deleteByGameUserId(createDto.gameUserId).catch(() => {});
        });
      });

      describe('findByGameUserId', () => {
        it('should return account if found in database', async () => {
          const testData = generateTestData('findByGameUserId');
          
          // 先创建一个账号
          const created = await service.create(testData);
          
          const result = await service.findByGameUserId(testData.gameUserId);

          expect(result).toBeDefined();
          expect(result?.gameUserId).toBe(testData.gameUserId);
          expect(result?.zulipEmail).toBe(testData.zulipEmail);

          // 清理测试数据
          await service.deleteByGameUserId(testData.gameUserId).catch(() => {});
        });

        it('should return null if not found in database', async () => {
          const result = await service.findByGameUserId('nonexistent_user_' + Date.now());
          expect(result).toBeNull();
        });
      });

      describe('existsByEmail', () => {
        it('should return true if email exists in database', async () => {
          const testData = generateTestData('existsEmail');
          
          // 先创建一个账号
          await service.create(testData);
          
          const result = await service.existsByEmail(testData.zulipEmail);
          expect(result).toBe(true);

          // 清理测试数据
          await service.deleteByGameUserId(testData.gameUserId).catch(() => {});
        });

        it('should return false if email does not exist in database', async () => {
          const result = await service.existsByEmail(`nonexistent_${Date.now()}@example.com`);
          expect(result).toBe(false);
        });
      });

      describe('getStatusStatistics', () => {
        it('should return status statistics from database', async () => {
          const result = await service.getStatusStatistics();

          expect(typeof result.active).toBe('number');
          expect(typeof result.inactive).toBe('number');
          expect(typeof result.suspended).toBe('number');
          expect(typeof result.error).toBe('number');
          expect(typeof result.total).toBe('number');
          expect(result.total).toBe(result.active + result.inactive + result.suspended + result.error);
        });
      });
    });
  } else {
    // Mock模式测试
    describe('Mock Mode Tests', () => {
      describe('create', () => {
        const createDto: CreateZulipAccountDto = {
          gameUserId: '12345',
          zulipUserId: 67890,
          zulipEmail: 'test@example.com',
          zulipFullName: '测试用户',
          zulipApiKeyEncrypted: 'encrypted_api_key',
          status: 'active',
        };

        it('should create a new account successfully with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).create.mockResolvedValue(mockAccount);

          const result = await service.create(createDto);

          expect(result).toBeDefined();
          expect(result.gameUserId).toBe('12345');
          expect(result.zulipEmail).toBe('test@example.com');
          expect((repository as jest.Mocked<ZulipAccountsRepository>).create).toHaveBeenCalledWith({
            gameUserId: BigInt(12345),
            zulipUserId: 67890,
            zulipEmail: 'test@example.com',
            zulipFullName: '测试用户',
            zulipApiKeyEncrypted: 'encrypted_api_key',
            status: 'active',
          });
        });

        it('should throw ConflictException if game user already has account with mock', async () => {
          const error = new Error('Game user 12345 already has a Zulip account');
          (repository as jest.Mocked<ZulipAccountsRepository>).create.mockRejectedValue(error);

          await expect(service.create(createDto)).rejects.toThrow(ConflictException);
        });
      });

      describe('findByGameUserId', () => {
        it('should return account if found with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).findByGameUserId.mockResolvedValue(mockAccount);

          const result = await service.findByGameUserId('12345');

          expect(result).toBeDefined();
          expect(result?.gameUserId).toBe('12345');
          expect((repository as jest.Mocked<ZulipAccountsRepository>).findByGameUserId).toHaveBeenCalledWith(BigInt(12345), false);
        });

        it('should return null if not found with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).findByGameUserId.mockResolvedValue(null);

          const result = await service.findByGameUserId('12345');
          expect(result).toBeNull();
        });
      });

      describe('existsByEmail', () => {
        it('should return true if email exists with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).existsByEmail.mockResolvedValue(true);

          const result = await service.existsByEmail('test@example.com');

          expect(result).toBe(true);
          expect((repository as jest.Mocked<ZulipAccountsRepository>).existsByEmail).toHaveBeenCalledWith('test@example.com', undefined);
        });

        it('should return false if email does not exist with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).existsByEmail.mockResolvedValue(false);

          const result = await service.existsByEmail('test@example.com');
          expect(result).toBe(false);
        });

        it('should return false on error with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).existsByEmail.mockRejectedValue(new Error('Database error'));

          const result = await service.existsByEmail('test@example.com');
          expect(result).toBe(false);
        });
      });

      describe('getStatusStatistics', () => {
        it('should return status statistics with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).getStatusStatistics.mockResolvedValue({
            active: 10,
            inactive: 5,
            suspended: 2,
            error: 1,
          });

          const result = await service.getStatusStatistics();

          expect(result.active).toBe(10);
          expect(result.inactive).toBe(5);
          expect(result.suspended).toBe(2);
          expect(result.error).toBe(1);
          expect(result.total).toBe(18);
        });
      });

      describe('findMany', () => {
        it('should return list of accounts with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).findMany.mockResolvedValue([mockAccount]);

          const result = await service.findMany({ status: 'active' });

          expect(result).toBeDefined();
          expect(result.accounts).toHaveLength(1);
          expect(result.total).toBe(1);
          expect(result.count).toBe(1);
        });

        it('should return empty list on error with mock', async () => {
          (repository as jest.Mocked<ZulipAccountsRepository>).findMany.mockRejectedValue(new Error('Database error'));

          const result = await service.findMany();

          expect(result.accounts).toHaveLength(0);
          expect(result.total).toBe(0);
          expect(result.count).toBe(0);
        });
      });
    });
  }
});