import { UserEntity } from '../../domain/entities/user.entity';
import { type IUserRepository } from '../../domain/repositories/user.repository';
import { Email } from '../../domain/value-objects/email.vo';
import { type HashedPassword } from '../../domain/value-objects/hashed-password.vo';
import { Phone } from '../../domain/value-objects/phone.vo';
import { GetProfileHandler } from '../queries/get-profile/get-profile.handler';
import { GetProfileQuery } from '../queries/get-profile/get-profile.query';

function createUserWithEmail(): UserEntity {
  const phone = Phone.create('0912345678').unwrap();
  const email = Email.create('test@example.com').unwrap();
  const pw = { value: 'hashed' } as HashedPassword;
  return new UserEntity('user-1', {
    email,
    phone,
    passwordHash: pw,
    fullName: 'Nguyen Van A',
    avatarUrl: 'https://example.com/avatar.jpg',
    role: 'BUYER',
    kycStatus: 'VERIFIED',
    kycData: null,
    isActive: true,
  });
}

function createUserWithoutEmail(): UserEntity {
  const phone = Phone.create('0912345678').unwrap();
  const pw = { value: 'hashed' } as HashedPassword;
  return new UserEntity('user-2', {
    email: null,
    phone,
    passwordHash: pw,
    fullName: 'Tran Thi B',
    avatarUrl: null,
    role: 'SELLER',
    kycStatus: 'NONE',
    kycData: null,
    isActive: true,
  });
}

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

  beforeEach(() => {
    mockUserRepo = {
      findById: vi.fn(),
      findByPhone: vi.fn(),
      findByEmail: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
    };
    // Pass-through cache: always invokes the loader
    mockCache = {
      getOrSet: vi.fn(async (_key: string, loader: () => Promise<unknown>) => loader()),
    };

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

  it('returns user profile with email', async () => {
    mockUserRepo.findById.mockResolvedValue(createUserWithEmail());

    const query = new GetProfileQuery('user-1');
    const result = await handler.execute(query);

    expect(result).toEqual({
      id: 'user-1',
      email: 'test@example.com',
      phone: '+84912345678',
      fullName: 'Nguyen Van A',
      avatarUrl: 'https://example.com/avatar.jpg',
      role: 'BUYER',
      kycStatus: 'VERIFIED',
      isActive: true,
      createdAt: expect.any(Date),
    });
  });

  it('returns null email when user has no email', async () => {
    mockUserRepo.findById.mockResolvedValue(createUserWithoutEmail());

    const query = new GetProfileQuery('user-2');
    const result = await handler.execute(query);

    expect(result.email).toBeNull();
    expect(result.fullName).toBe('Tran Thi B');
  });

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

    const query = new GetProfileQuery('non-existent');

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

  it('uses cache with correct key', async () => {
    mockUserRepo.findById.mockResolvedValue(createUserWithEmail());

    const query = new GetProfileQuery('user-1');
    await handler.execute(query);

    expect(mockCache.getOrSet).toHaveBeenCalledWith(
      expect.stringContaining('user-1'),
      expect.any(Function),
      expect.any(Number),
      'user_profile',
    );
  });
});
