/**
 * Zulip账号关联服务测试
 * 
 * 功能描述：
 * - 测试ZulipAccountsService的核心功能
 * - 测试CRUD操作和业务逻辑
 * - 测试异常处理和边界情况
 * 
 * @author angjustinl
 * @version 1.0.0
 * @since 2025-01-07
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException } from '@nestjs/common';
import { ZulipAccountsService } from './zulip_accounts.service';
import { ZulipAccountsRepository } from './zulip_accounts.repository';
import { ZulipAccounts } from './zulip_accounts.entity';
import { CreateZulipAccountDto, UpdateZulipAccountDto } from './zulip_accounts.dto';

describe('ZulipAccountsService', () => {
  let service: ZulipAccountsService;
  let repository: jest.Mocked<ZulipAccountsRepository>;

  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(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ZulipAccountsService,
        {
          provide: 'ZulipAccountsRepository',
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get<ZulipAccountsService>(ZulipAccountsService);
    repository = module.get('ZulipAccountsRepository');
  });

  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 ConflictException 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(ConflictException);
    });

    it('should throw ConflictException if zulip user ID already exists', async () => {
      const error = new Error('Zulip user 67890 is already linked');
      repository.create.mockRejectedValue(error);

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

    it('should throw ConflictException if zulip email already exists', async () => {
      const error = new Error('Zulip email test@example.com is already linked');
      repository.create.mockRejectedValue(error);

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

  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();
    });
  });

  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 throw NotFoundException if not found', async () => {
      repository.findById.mockResolvedValue(null);

      await expect(service.findById('1')).rejects.toThrow(NotFoundException);
    });
  });

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

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

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

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

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

      await expect(service.update('1', updateDto)).rejects.toThrow(NotFoundException);
    });
  });

  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 throw NotFoundException if account not found', async () => {
      repository.delete.mockResolvedValue(false);

      await expect(service.delete('1')).rejects.toThrow(NotFoundException);
    });
  });

  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('Database error'));

      const result = await service.findMany();

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

  describe('batchUpdateStatus', () => {
    it('should update multiple accounts successfully', async () => {
      repository.batchUpdateStatus.mockResolvedValue(3);

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

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

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

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

      expect(result.success).toBe(false);
      expect(result.updatedCount).toBe(0);
      expect(result.error).toBeDefined();
    });
  });

  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 invalid 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 invalid if account status is not active', async () => {
      const inactiveAccount = Object.assign(Object.create(ZulipAccounts.prototype), {
        ...mockAccount, 
        status: 'inactive' as const
      });
      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('Database 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('Database error'));

      const result = await service.existsByZulipUserId(67890);

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