/**
 * Zulip账号关联内存服务测试
 * 
 * 功能描述：
 * - 测试内存版本的Zulip账号关联服务
 * - 验证内存存储的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 { ConflictException, NotFoundException } from '@nestjs/common';
import { ZulipAccountsMemoryService } from './zulip_accounts_memory.service';
import { ZulipAccountsMemoryRepository } from './zulip_accounts_memory.repository';
import { ZulipAccounts } from './zulip_accounts.entity';
import { AppLoggerService } from '../../utils/logger/logger.service';
import { CreateZulipAccountDto, UpdateZulipAccountDto } from './zulip_accounts.dto';

describe('ZulipAccountsMemoryService', () => {
  let service: ZulipAccountsMemoryService;
  let repository: jest.Mocked<ZulipAccountsMemoryRepository>;
  let logger: jest.Mocked<AppLoggerService>;

  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 () => {
    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 module: TestingModule = await Test.createTestingModule({
      providers: [
        ZulipAccountsMemoryService,
        {
          provide: 'ZulipAccountsRepository',
          useValue: mockRepository,
        },
        {
          provide: AppLoggerService,
          useValue: mockLogger,
        },
      ],
    }).compile();

    service = module.get<ZulipAccountsMemoryService>(ZulipAccountsMemoryService);
    repository = module.get('ZulipAccountsRepository');
    logger = module.get(AppLoggerService);
  });

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

  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', async () => {
      repository.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.create).toHaveBeenCalledWith({
        gameUserId: BigInt(12345),
        zulipUserId: 67890,
        zulipEmail: 'test@example.com',
        zulipFullName: '测试用户',
        zulipApiKeyEncrypted: 'encrypted_api_key',
        status: 'active',
      });
    });

    it('should throw error if game user already has account', async () => {
      const error = new Error('Game user 12345 already has a Zulip account');
      repository.create.mockRejectedValue(error);

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

  describe('findByGameUserId', () => {
    it('should return account if found', async () => {
      repository.findByGameUserId.mockResolvedValue(mockAccount);

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

      expect(result).toBeDefined();
      expect(result?.gameUserId).toBe('12345');
      expect(repository.findByGameUserId).toHaveBeenCalledWith(BigInt(12345), false);
    });

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

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

    it('should handle includeGameUser parameter', async () => {
      repository.findByGameUserId.mockResolvedValue(mockAccount);

      await service.findByGameUserId('12345', true);

      expect(repository.findByGameUserId).toHaveBeenCalledWith(BigInt(12345), true);
    });
  });

  describe('findByZulipUserId', () => {
    it('should return account if found', async () => {
      repository.findByZulipUserId.mockResolvedValue(mockAccount);

      const result = await service.findByZulipUserId(67890);

      expect(result).toBeDefined();
      expect(result?.zulipUserId).toBe(67890);
      expect(repository.findByZulipUserId).toHaveBeenCalledWith(67890, false);
    });

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

      const result = await service.findByZulipUserId(67890);
      expect(result).toBeNull();
    });
  });

  describe('findByZulipEmail', () => {
    it('should return account if found', async () => {
      repository.findByZulipEmail.mockResolvedValue(mockAccount);

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

      expect(result).toBeDefined();
      expect(result?.zulipEmail).toBe('test@example.com');
      expect(repository.findByZulipEmail).toHaveBeenCalledWith('test@example.com', false);
    });

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

      const result = await service.findByZulipEmail('test@example.com');
      expect(result).toBeNull();
    });
  });

  describe('findById', () => {
    it('should return account if found', async () => {
      repository.findById.mockResolvedValue(mockAccount);

      const result = await service.findById('1');

      expect(result).toBeDefined();
      expect(result?.id).toBe('1');
      expect(repository.findById).toHaveBeenCalledWith(BigInt(1), false);
    });

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

      const result = await service.findById('1');
      expect(result).toBeNull();
    });
  });

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

    it('should update account successfully', async () => {
      const updatedAccount = { 
        ...mockAccount, 
        zulipFullName: '更新的用户名',
        status: 'inactive' as const
      } as ZulipAccounts;
      repository.update.mockResolvedValue(updatedAccount);

      const result = await service.update('1', updateDto);

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

    it('should return null if account not found', async () => {
      repository.update.mockResolvedValue(null);

      const result = await service.update('1', updateDto);
      expect(result).toBeNull();
    });
  });

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

    it('should update account by game user ID successfully', async () => {
      const updatedAccount = { 
        ...mockAccount, 
        status: 'suspended' as const
      } as ZulipAccounts;
      repository.updateByGameUserId.mockResolvedValue(updatedAccount);

      const result = await service.updateByGameUserId('12345', updateDto);

      expect(result).toBeDefined();
      expect(result?.status).toBe('suspended');
      expect(repository.updateByGameUserId).toHaveBeenCalledWith(BigInt(12345), updateDto);
    });
  });

  describe('delete', () => {
    it('should delete account successfully', async () => {
      repository.delete.mockResolvedValue(true);

      const result = await service.delete('1');

      expect(result).toBe(true);
      expect(repository.delete).toHaveBeenCalledWith(BigInt(1));
    });

    it('should return false if account not found', async () => {
      repository.delete.mockResolvedValue(false);

      const result = await service.delete('1');
      expect(result).toBe(false);
    });
  });

  describe('deleteByGameUserId', () => {
    it('should delete account by game user ID successfully', async () => {
      repository.deleteByGameUserId.mockResolvedValue(true);

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

      expect(result).toBe(true);
      expect(repository.deleteByGameUserId).toHaveBeenCalledWith(BigInt(12345));
    });
  });

  describe('findMany', () => {
    it('should return list of accounts', async () => {
      repository.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', async () => {
      repository.findMany.mockRejectedValue(new Error('Repository error'));

      const result = await service.findMany();

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

  describe('getStatusStatistics', () => {
    it('should return status statistics', async () => {
      repository.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('verifyAccount', () => {
    it('should verify account successfully', async () => {
      repository.findByGameUserId.mockResolvedValue(mockAccount);
      repository.updateByGameUserId.mockResolvedValue(mockAccount);

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

      expect(result.success).toBe(true);
      expect(result.isValid).toBe(true);
      expect(result.verifiedAt).toBeDefined();
    });

    it('should return error if account not found', async () => {
      repository.findByGameUserId.mockResolvedValue(null);

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

      expect(result.success).toBe(false);
      expect(result.isValid).toBe(false);
      expect(result.error).toBe('账号关联不存在');
    });

    it('should return error if account is not active', async () => {
      const inactiveAccount = { 
        ...mockAccount, 
        status: 'inactive' as const
      } as ZulipAccounts;
      repository.findByGameUserId.mockResolvedValue(inactiveAccount);

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

      expect(result.success).toBe(true);
      expect(result.isValid).toBe(false);
      expect(result.error).toBe('账号状态为 inactive');
    });
  });

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

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

      expect(result).toBe(true);
      expect(repository.existsByEmail).toHaveBeenCalledWith('test@example.com', undefined);
    });

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

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

    it('should return false on error', async () => {
      repository.existsByEmail.mockRejectedValue(new Error('Repository error'));

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

  describe('existsByZulipUserId', () => {
    it('should return true if Zulip user ID exists', async () => {
      repository.existsByZulipUserId.mockResolvedValue(true);

      const result = await service.existsByZulipUserId(67890);

      expect(result).toBe(true);
      expect(repository.existsByZulipUserId).toHaveBeenCalledWith(67890, undefined);
    });

    it('should return false if Zulip user ID does not exist', async () => {
      repository.existsByZulipUserId.mockResolvedValue(false);

      const result = await service.existsByZulipUserId(67890);
      expect(result).toBe(false);
    });

    it('should return false on error', async () => {
      repository.existsByZulipUserId.mockRejectedValue(new Error('Repository error'));

      const result = await service.existsByZulipUserId(67890);
      expect(result).toBe(false);
    });
  });

  describe('batchUpdateStatus', () => {
    it('should update status for multiple accounts', async () => {
      repository.batchUpdateStatus.mockResolvedValue(2);

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

      expect(result.success).toBe(true);
      expect(result.updatedCount).toBe(2);
      expect(repository.batchUpdateStatus).toHaveBeenCalledWith([BigInt(1), BigInt(2)], 'suspended');
    });

    it('should handle batch update error', async () => {
      repository.batchUpdateStatus.mockRejectedValue(new Error('Batch update failed'));

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

      expect(result.success).toBe(false);
      expect(result.updatedCount).toBe(0);
      expect(result.error).toBe('Batch update failed');
    });
  });
});