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 { RefreshTokenCommand } from '../commands/refresh-token/refresh-token.command';
import { RefreshTokenHandler } from '../commands/refresh-token/refresh-token.handler';

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

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

describe('RefreshTokenHandler', () => {
  let handler: RefreshTokenHandler;
  let mockTokenService: {
    rotateRefreshToken: ReturnType<typeof vi.fn>;
    generateAccessToken: ReturnType<typeof vi.fn>;
  };
  let mockUserRepo: { [K in keyof IUserRepository]: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockTokenService = {
      rotateRefreshToken: vi.fn(),
      generateAccessToken: vi.fn().mockReturnValue('new-access-jwt'),
    };
    mockUserRepo = {
      findById: vi.fn(),
      findByPhone: vi.fn(),
      findByEmail: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
    };

    handler = new RefreshTokenHandler(
      mockTokenService as any,
      mockUserRepo as any,
    );
  });

  it('rotates refresh token and returns new token pair', async () => {
    mockTokenService.rotateRefreshToken.mockResolvedValue({
      userId: 'user-1',
      refreshToken: 'new-family.new-refresh-hex',
    });
    mockUserRepo.findById.mockResolvedValue(createActiveUser());

    const command = new RefreshTokenCommand('old-family.old-refresh-hex');
    const result = await handler.execute(command);

    expect(result.accessToken).toBe('new-access-jwt');
    expect(result.refreshToken).toBe('new-family.new-refresh-hex');
    expect(result.expiresIn).toBe(900);
    expect(mockTokenService.generateAccessToken).toHaveBeenCalledWith(
      expect.objectContaining({ sub: 'user-1', phone: '+84912345678', role: 'BUYER' }),
    );
  });

  it('throws UnauthorizedException when refresh token is invalid', async () => {
    mockTokenService.rotateRefreshToken.mockResolvedValue(null);

    const command = new RefreshTokenCommand('invalid-token');

    await expect(handler.execute(command)).rejects.toThrow(
      'Refresh token không hợp lệ hoặc đã hết hạn',
    );
  });

  it('throws UnauthorizedException when user not found', async () => {
    mockTokenService.rotateRefreshToken.mockResolvedValue({
      userId: 'deleted-user',
      refreshToken: 'new-family.new-hex',
    });
    mockUserRepo.findById.mockResolvedValue(null);

    const command = new RefreshTokenCommand('family.valid-hex');

    await expect(handler.execute(command)).rejects.toThrow(
      'Tài khoản không tồn tại hoặc đã bị vô hiệu hóa',
    );
  });

  it('throws UnauthorizedException when user is deactivated', async () => {
    mockTokenService.rotateRefreshToken.mockResolvedValue({
      userId: 'user-1',
      refreshToken: 'new-family.new-hex',
    });
    mockUserRepo.findById.mockResolvedValue(createInactiveUser());

    const command = new RefreshTokenCommand('family.valid-hex');

    await expect(handler.execute(command)).rejects.toThrow(
      'Tài khoản không tồn tại hoặc đã bị vô hiệu hóa',
    );
  });
});
