import { UserEntity } from '../../domain/entities/user.entity';
import { type IUserRepository } from '../../domain/repositories/user.repository';
import { type HashedPassword } from '../../domain/value-objects/hashed-password.vo';
import { Phone } from '../../domain/value-objects/phone.vo';
import { VerifyKycCommand } from '../commands/verify-kyc/verify-kyc.command';
import { VerifyKycHandler } from '../commands/verify-kyc/verify-kyc.handler';

function createTestUser(): UserEntity {
  const phone = Phone.create('0912345678').unwrap();
  const pw = { value: 'hashed' } as HashedPassword;
  return new UserEntity('user-1', {
    email: null,
    phone,
    passwordHash: pw,
    fullName: 'Nguyen Van A',
    avatarUrl: null,
    role: 'BUYER',
    kycStatus: 'NONE',
    kycData: null,
    isActive: true,
  });
}

describe('VerifyKycHandler', () => {
  let handler: VerifyKycHandler;
  let mockUserRepo: { [K in keyof IUserRepository]: ReturnType<typeof vi.fn> };
  let mockCache: { invalidate: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockUserRepo = {
      findById: vi.fn(),
      findByPhone: vi.fn(),
      findByEmail: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
    };
    mockCache = { invalidate: vi.fn().mockResolvedValue(undefined) };

    handler = new VerifyKycHandler(mockUserRepo as any, mockCache as any);
  });

  it('updates KYC status and invalidates cache', async () => {
    const user = createTestUser();
    mockUserRepo.findById.mockResolvedValue(user);
    mockUserRepo.update.mockResolvedValue(undefined);

    const command = new VerifyKycCommand('user-1', 'VERIFIED', { documentId: 'doc-123' });
    await handler.execute(command);

    expect(mockUserRepo.findById).toHaveBeenCalledWith('user-1');
    expect(mockUserRepo.update).toHaveBeenCalledWith(user);
    expect(mockCache.invalidate).toHaveBeenCalledWith(
      expect.stringContaining('user-1'),
    );
  });

  it('updates KYC status without optional kycData', async () => {
    const user = createTestUser();
    mockUserRepo.findById.mockResolvedValue(user);
    mockUserRepo.update.mockResolvedValue(undefined);

    const command = new VerifyKycCommand('user-1', 'REJECTED');
    await handler.execute(command);

    expect(mockUserRepo.update).toHaveBeenCalledWith(user);
    expect(mockCache.invalidate).toHaveBeenCalled();
  });

  it('throws NotFoundException when user does not exist', async () => {
    mockUserRepo.findById.mockResolvedValue(null);

    const command = new VerifyKycCommand('non-existent', 'VERIFIED');

    await expect(handler.execute(command)).rejects.toThrow('Người dùng');
  });

  it('does not update repository when user is not found', async () => {
    mockUserRepo.findById.mockResolvedValue(null);

    const command = new VerifyKycCommand('non-existent', 'VERIFIED');

    await expect(handler.execute(command)).rejects.toThrow();
    expect(mockUserRepo.update).not.toHaveBeenCalled();
    expect(mockCache.invalidate).not.toHaveBeenCalled();
  });
});
