/**
 * Zulip账号关联数据访问层测试
 * 
 * 功能描述：
 * - 测试Repository层的数据访问逻辑
 * - 验证CRUD操作和查询方法
 * - 测试事务处理和并发控制
 * - 测试查询优化和性能监控
 * - 确保数据访问层的正确性和健壮性
 * 
 * 最近修改：
 * - 2026-01-12: 代码规范优化 - 创建缺失的测试文件，确保测试覆盖完整性 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-12
 * @lastModified 2026-01-12
 */

import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository, DataSource, SelectQueryBuilder } from 'typeorm';
import { ZulipAccountsRepository } from './zulip_accounts.repository';
import { ZulipAccounts } from './zulip_accounts.entity';
import { AppLoggerService } from '../../utils/logger/logger.service';
import {
  CreateZulipAccountData,
  UpdateZulipAccountData,
  ZulipAccountQueryOptions,
} from './zulip_accounts.types';

describe('ZulipAccountsRepository', () => {
  let repository: ZulipAccountsRepository;
  let typeormRepository: jest.Mocked<Repository<ZulipAccounts>>;
  let dataSource: jest.Mocked<DataSource>;
  let logger: jest.Mocked<AppLoggerService>;
  let queryBuilder: jest.Mocked<SelectQueryBuilder<ZulipAccounts>>;

  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 () => {
    // Mock QueryBuilder
    queryBuilder = {
      where: jest.fn().mockReturnThis(),
      andWhere: jest.fn().mockReturnThis(),
      orWhere: jest.fn().mockReturnThis(),
      orderBy: jest.fn().mockReturnThis(),
      addOrderBy: jest.fn().mockReturnThis(),
      limit: jest.fn().mockReturnThis(),
      offset: jest.fn().mockReturnThis(),
      setLock: jest.fn().mockReturnThis(),
      leftJoinAndSelect: jest.fn().mockReturnThis(),
      select: jest.fn().mockReturnThis(),
      addSelect: jest.fn().mockReturnThis(),
      groupBy: jest.fn().mockReturnThis(),
      getOne: jest.fn(),
      getMany: jest.fn(),
      getCount: jest.fn(),
      getRawMany: jest.fn(),
      execute: jest.fn(),
      update: jest.fn().mockReturnThis(),
      set: jest.fn().mockReturnThis(),
      whereInIds: jest.fn().mockReturnThis(),
    } as any;

    // Mock TypeORM Repository
    const mockTypeormRepository = {
      create: jest.fn(),
      save: jest.fn(),
      findOne: jest.fn(),
      find: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
    };

    // Mock DataSource with transaction support
    const mockDataSource = {
      transaction: jest.fn(),
      createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
    };

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

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ZulipAccountsRepository,
        {
          provide: getRepositoryToken(ZulipAccounts),
          useValue: mockTypeormRepository,
        },
        {
          provide: DataSource,
          useValue: mockDataSource,
        },
        {
          provide: AppLoggerService,
          useValue: mockLogger,
        },
      ],
    }).compile();

    repository = module.get<ZulipAccountsRepository>(ZulipAccountsRepository);
    typeormRepository = module.get(getRepositoryToken(ZulipAccounts));
    dataSource = module.get(DataSource);
    logger = module.get(AppLoggerService);
  });

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

  describe('create', () => {
    const createDto: CreateZulipAccountData = {
      gameUserId: BigInt(12345),
      zulipUserId: 67890,
      zulipEmail: 'test@example.com',
      zulipFullName: '测试用户',
      zulipApiKeyEncrypted: 'encrypted_api_key',
      status: 'active',
    };

    it('should create account successfully with transaction', async () => {
      const mockManager = {
        createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
        create: jest.fn().mockReturnValue(mockAccount),
        save: jest.fn().mockResolvedValue(mockAccount),
      };

      queryBuilder.getOne.mockResolvedValue(null); // No existing records
      dataSource.transaction.mockImplementation(async (callback: any) => {
        return await callback(mockManager);
      });

      const result = await repository.create(createDto);

      expect(result).toEqual(mockAccount);
      expect(dataSource.transaction).toHaveBeenCalled();
      expect(mockManager.create).toHaveBeenCalledWith(ZulipAccounts, createDto);
      expect(mockManager.save).toHaveBeenCalledWith(mockAccount);
      expect(logger.info).toHaveBeenCalledWith(
        '创建Zulip账号关联成功',
        expect.objectContaining({
          module: 'ZulipAccountsRepository',
          operation: 'create',
          gameUserId: '12345',
          accountId: '1',
        })
      );
    });

    it('should throw error if game user already exists', async () => {
      const mockManager = {
        createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
      };

      queryBuilder.getOne.mockResolvedValueOnce(mockAccount); // Existing game user
      dataSource.transaction.mockImplementation(async (callback: any) => {
        return await callback(mockManager);
      });

      await expect(repository.create(createDto)).rejects.toThrow(
        'Game user 12345 already has a Zulip account'
      );
    });

    it('should throw error if zulip user already exists', async () => {
      const mockManager = {
        createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
      };

      queryBuilder.getOne
        .mockResolvedValueOnce(null) // No existing game user
        .mockResolvedValueOnce(mockAccount); // Existing zulip user

      dataSource.transaction.mockImplementation(async (callback: any) => {
        return await callback(mockManager);
      });

      await expect(repository.create(createDto)).rejects.toThrow(
        'Zulip user 67890 is already linked'
      );
    });

    it('should throw error if email already exists', async () => {
      const mockManager = {
        createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
      };

      queryBuilder.getOne
        .mockResolvedValueOnce(null) // No existing game user
        .mockResolvedValueOnce(null) // No existing zulip user
        .mockResolvedValueOnce(mockAccount); // Existing email

      dataSource.transaction.mockImplementation(async (callback: any) => {
        return await callback(mockManager);
      });

      await expect(repository.create(createDto)).rejects.toThrow(
        'Zulip email test@example.com is already linked'
      );
    });
  });

  describe('findByGameUserId', () => {
    it('should find account by game user ID', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findByGameUserId(BigInt(12345));

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { gameUserId: BigInt(12345) },
        relations: [],
      });
    });

    it('should find account with game user relation', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findByGameUserId(BigInt(12345), true);

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { gameUserId: BigInt(12345) },
        relations: ['gameUser'],
      });
    });

    it('should return null if not found', async () => {
      typeormRepository.findOne.mockResolvedValue(null);

      const result = await repository.findByGameUserId(BigInt(12345));

      expect(result).toBeNull();
    });
  });

  describe('findByZulipUserId', () => {
    it('should find account by zulip user ID', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findByZulipUserId(67890);

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { zulipUserId: 67890 },
        relations: [],
      });
    });

    it('should find account with game user relation', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findByZulipUserId(67890, true);

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { zulipUserId: 67890 },
        relations: ['gameUser'],
      });
    });
  });

  describe('findByZulipEmail', () => {
    it('should find account by zulip email', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findByZulipEmail('test@example.com');

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { zulipEmail: 'test@example.com' },
        relations: [],
      });
    });
  });

  describe('findById', () => {
    it('should find account by ID', async () => {
      typeormRepository.findOne.mockResolvedValue(mockAccount);

      const result = await repository.findById(BigInt(1));

      expect(result).toEqual(mockAccount);
      expect(typeormRepository.findOne).toHaveBeenCalledWith({
        where: { id: BigInt(1) },
        relations: [],
      });
    });
  });

  describe('update', () => {
    const updateDto: UpdateZulipAccountData = {
      zulipFullName: '更新的用户名',
      status: 'inactive',
    };

    it('should update account successfully', async () => {
      typeormRepository.update.mockResolvedValue({ affected: 1 } as any);
      typeormRepository.findOne.mockResolvedValue({
        ...mockAccount,
        zulipFullName: '更新的用户名',
        status: 'inactive',
      } as ZulipAccounts);

      const result = await repository.update(BigInt(1), updateDto);

      expect(result).toBeDefined();
      expect(result?.zulipFullName).toBe('更新的用户名');
      expect(typeormRepository.update).toHaveBeenCalledWith({ id: BigInt(1) }, updateDto);
    });

    it('should return null if no records affected', async () => {
      typeormRepository.update.mockResolvedValue({ affected: 0 } as any);

      const result = await repository.update(BigInt(1), updateDto);

      expect(result).toBeNull();
    });
  });

  describe('updateByGameUserId', () => {
    const updateDto: UpdateZulipAccountData = {
      status: 'suspended',
    };

    it('should update account by game user ID', async () => {
      typeormRepository.update.mockResolvedValue({ affected: 1 } as any);
      typeormRepository.findOne.mockResolvedValue({
        ...mockAccount,
        status: 'suspended',
      } as ZulipAccounts);

      const result = await repository.updateByGameUserId(BigInt(12345), updateDto);

      expect(result).toBeDefined();
      expect(result?.status).toBe('suspended');
      expect(typeormRepository.update).toHaveBeenCalledWith({ gameUserId: BigInt(12345) }, updateDto);
    });
  });

  describe('delete', () => {
    it('should delete account successfully', async () => {
      typeormRepository.delete.mockResolvedValue({ affected: 1 } as any);

      const result = await repository.delete(BigInt(1));

      expect(result).toBe(true);
      expect(typeormRepository.delete).toHaveBeenCalledWith({ id: BigInt(1) });
    });

    it('should return false if no records affected', async () => {
      typeormRepository.delete.mockResolvedValue({ affected: 0 } as any);

      const result = await repository.delete(BigInt(1));

      expect(result).toBe(false);
    });
  });

  describe('deleteByGameUserId', () => {
    it('should delete account by game user ID', async () => {
      typeormRepository.delete.mockResolvedValue({ affected: 1 } as any);

      const result = await repository.deleteByGameUserId(BigInt(12345));

      expect(result).toBe(true);
      expect(typeormRepository.delete).toHaveBeenCalledWith({ gameUserId: BigInt(12345) });
    });
  });

  describe('findMany', () => {
    const queryOptions: ZulipAccountQueryOptions = {
      gameUserId: BigInt(12345),
      status: 'active',
      includeGameUser: true,
    };

    it('should find many accounts with query options', async () => {
      queryBuilder.getMany.mockResolvedValue([mockAccount]);

      const result = await repository.findMany(queryOptions);

      expect(result).toEqual([mockAccount]);
      expect(typeormRepository.createQueryBuilder).toHaveBeenCalledWith('za');
      expect(queryBuilder.andWhere).toHaveBeenCalledWith('za.gameUserId = :gameUserId', { gameUserId: BigInt(12345) });
      expect(queryBuilder.andWhere).toHaveBeenCalledWith('za.status = :status', { status: 'active' });
      expect(queryBuilder.leftJoinAndSelect).toHaveBeenCalledWith('za.gameUser', 'user');
      expect(logger.debug).toHaveBeenCalledWith(
        '查询多个Zulip账号关联完成',
        expect.objectContaining({
          resultCount: 1,
        })
      );
    });

    it('should handle empty query options', async () => {
      queryBuilder.getMany.mockResolvedValue([]);

      const result = await repository.findMany({});

      expect(result).toEqual([]);
      expect(queryBuilder.getMany).toHaveBeenCalled();
    });

    it('should handle query error', async () => {
      const error = new Error('Database error');
      queryBuilder.getMany.mockRejectedValue(error);

      await expect(repository.findMany(queryOptions)).rejects.toThrow(error);
      expect(logger.error).toHaveBeenCalledWith(
        '查询多个Zulip账号关联失败',
        expect.objectContaining({
          error: 'Database error',
        }),
        expect.any(String)
      );
    });
  });

  describe('findAccountsNeedingVerification', () => {
    it('should find accounts needing verification', async () => {
      queryBuilder.getMany.mockResolvedValue([mockAccount]);

      const result = await repository.findAccountsNeedingVerification(86400000); // 24 hours

      expect(result).toEqual([mockAccount]);
      expect(queryBuilder.where).toHaveBeenCalledWith('za.status = :status', { status: 'active' });
      expect(queryBuilder.andWhere).toHaveBeenCalledWith(
        '(za.last_verified_at IS NULL OR za.last_verified_at < :cutoffTime)',
        expect.objectContaining({ cutoffTime: expect.any(Date) })
      );
      expect(queryBuilder.limit).toHaveBeenCalledWith(100);
    });
  });

  describe('findErrorAccounts', () => {
    it('should find error accounts that can be retried', async () => {
      queryBuilder.getMany.mockResolvedValue([mockAccount]);

      const result = await repository.findErrorAccounts(3);

      expect(result).toEqual([mockAccount]);
      expect(queryBuilder.where).toHaveBeenCalledWith('za.status = :status', { status: 'error' });
      expect(queryBuilder.andWhere).toHaveBeenCalledWith('za.retry_count < :maxRetryCount', { maxRetryCount: 3 });
      expect(queryBuilder.limit).toHaveBeenCalledWith(50);
    });
  });

  describe('batchUpdateStatus', () => {
    it('should batch update status', async () => {
      queryBuilder.execute.mockResolvedValue({ affected: 2 });

      const result = await repository.batchUpdateStatus([BigInt(1), BigInt(2)], 'suspended');

      expect(result).toBe(2);
      expect(typeormRepository.createQueryBuilder).toHaveBeenCalled();
      expect(queryBuilder.update).toHaveBeenCalledWith(ZulipAccounts);
      expect(queryBuilder.whereInIds).toHaveBeenCalledWith([BigInt(1), BigInt(2)]);
    });

    it('should return 0 if no records affected', async () => {
      queryBuilder.execute.mockResolvedValue({ affected: 0 });

      const result = await repository.batchUpdateStatus([BigInt(1)], 'active');

      expect(result).toBe(0);
    });
  });

  describe('getStatusStatistics', () => {
    it('should get status statistics', async () => {
      const mockStats = [
        { status: 'active', count: '10' },
        { status: 'inactive', count: '5' },
        { status: 'suspended', count: '2' },
        { status: 'error', count: '1' },
      ];
      queryBuilder.getRawMany.mockResolvedValue(mockStats);

      const result = await repository.getStatusStatistics();

      expect(result).toEqual({
        active: 10,
        inactive: 5,
        suspended: 2,
        error: 1,
      });
      expect(queryBuilder.select).toHaveBeenCalledWith('za.status', 'status');
      expect(queryBuilder.addSelect).toHaveBeenCalledWith('COUNT(*)', 'count');
      expect(queryBuilder.groupBy).toHaveBeenCalledWith('za.status');
    });

    it('should return zero statistics if no data', async () => {
      queryBuilder.getRawMany.mockResolvedValue([]);

      const result = await repository.getStatusStatistics();

      expect(result).toEqual({
        active: 0,
        inactive: 0,
        suspended: 0,
        error: 0,
      });
    });
  });

  describe('existsByEmail', () => {
    it('should return true if email exists', async () => {
      queryBuilder.getCount.mockResolvedValue(1);

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

      expect(result).toBe(true);
      expect(queryBuilder.where).toHaveBeenCalledWith('za.zulip_email = :zulipEmail', { zulipEmail: 'test@example.com' });
    });

    it('should return false if email does not exist', async () => {
      queryBuilder.getCount.mockResolvedValue(0);

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

      expect(result).toBe(false);
    });

    it('should exclude specified ID', async () => {
      queryBuilder.getCount.mockResolvedValue(0);

      const result = await repository.existsByEmail('test@example.com', BigInt(1));

      expect(result).toBe(false);
      expect(queryBuilder.andWhere).toHaveBeenCalledWith('za.id != :excludeId', { excludeId: BigInt(1) });
    });
  });

  describe('existsByZulipUserId', () => {
    it('should return true if zulip user ID exists', async () => {
      queryBuilder.getCount.mockResolvedValue(1);

      const result = await repository.existsByZulipUserId(67890);

      expect(result).toBe(true);
      expect(queryBuilder.where).toHaveBeenCalledWith('za.zulip_user_id = :zulipUserId', { zulipUserId: 67890 });
    });

    it('should return false if zulip user ID does not exist', async () => {
      queryBuilder.getCount.mockResolvedValue(0);

      const result = await repository.existsByZulipUserId(67890);

      expect(result).toBe(false);
    });
  });

  describe('existsByGameUserId', () => {
    it('should return true if game user ID exists', async () => {
      queryBuilder.getCount.mockResolvedValue(1);

      const result = await repository.existsByGameUserId(BigInt(12345));

      expect(result).toBe(true);
      expect(queryBuilder.where).toHaveBeenCalledWith('za.game_user_id = :gameUserId', { gameUserId: BigInt(12345) });
    });

    it('should return false if game user ID does not exist', async () => {
      queryBuilder.getCount.mockResolvedValue(0);

      const result = await repository.existsByGameUserId(BigInt(12345));

      expect(result).toBe(false);
    });
  });
});