@VisibleForTesting
  CompletableFuture<Optional<SendPushNotificationResult>> sendNotification(final PushNotification pushNotification) {
    if (pushNotification.tokenType() == PushNotification.TokenType.APN && !pushNotification.urgent()) {
      // APNs imposes a per-device limit on background push notifications; schedule a notification for some time in the
      // future (possibly even now!) rather than sending a notification directly
      return pushNotificationScheduler
          .scheduleBackgroundApnsNotification(pushNotification.destination(), pushNotification.destinationDevice())
          .whenComplete(logErrors())
          .thenApply(ignored -> Optional.<SendPushNotificationResult>empty())
          .toCompletableFuture();
    }

    final PushNotificationSender sender = switch (pushNotification.tokenType()) {
      case FCM -> fcmSender;
      case APN, APN_VOIP -> apnSender;
    };

    return sender.sendNotification(pushNotification).whenComplete((result, throwable) -> {
      if (throwable == null) {
        Tags tags = Tags.of("tokenType", pushNotification.tokenType().name(),
            "notificationType", pushNotification.notificationType().name(),
            "urgent", String.valueOf(pushNotification.urgent()),
            "accepted", String.valueOf(result.accepted()),
            "unregistered", String.valueOf(result.unregistered()));

        if (result.errorCode().isPresent()) {
          tags = tags.and("errorCode", result.errorCode().get());
        }

        Metrics.counter(SENT_NOTIFICATION_COUNTER_NAME, tags).increment();

        if (result.unregistered() && pushNotification.destination() != null
            && pushNotification.destinationDevice() != null) {

          handleDeviceUnregistered(pushNotification.destination(),
              pushNotification.destinationDevice(),
              pushNotification.tokenType(),
              result.errorCode(),
              result.unregisteredTimestamp());
        }

        if (result.accepted() &&
            pushNotification.tokenType() == PushNotification.TokenType.APN_VOIP &&
            pushNotification.notificationType() == PushNotification.NotificationType.NOTIFICATION &&
            pushNotification.destination() != null &&
            pushNotification.destinationDevice() != null) {

          pushNotificationScheduler.scheduleRecurringApnsVoipNotification(
                  pushNotification.destination(),
                  pushNotification.destinationDevice())
              .whenComplete(logErrors());
        }
      } else {
        logger.debug("Failed to deliver {} push notification to {} ({})",
            pushNotification.notificationType(), pushNotification.deviceToken(), pushNotification.tokenType(),
            throwable);

        Metrics.counter(FAILED_NOTIFICATION_COUNTER_NAME, "cause", throwable.getClass().getSimpleName()).increment();
      }
    })
        .thenApply(Optional::of);
  }
@Test
  void testSendNotificationUnregisteredApn() {
    final Account account = mock(Account.class);
    final Device device = mock(Device.class);
    final UUID aci = UUID.randomUUID();
    when(device.getId()).thenReturn(Device.PRIMARY_ID);
    when(device.getApnId()).thenReturn("apns-token");
    when(device.getVoipApnId()).thenReturn("apns-voip-token");
    when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device));
    when(account.getUuid()).thenReturn(aci);
    when(accountsManager.getByAccountIdentifier(aci)).thenReturn(Optional.of(account));

    final PushNotification pushNotification = new PushNotification(
        "token", PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device, true);

    when(apnSender.sendNotification(pushNotification))
        .thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(false, Optional.empty(), true, Optional.empty())));

    when(pushNotificationScheduler.cancelScheduledNotifications(account, device))
        .thenReturn(CompletableFuture.completedFuture(null));

    pushNotificationManager.sendNotification(pushNotification);

    verifyNoInteractions(fcmSender);
    verify(accountsManager).updateDevice(eq(account), eq(Device.PRIMARY_ID), any());
    verify(device).setVoipApnId(null);
    verify(device, never()).setApnId(any());
    verify(pushNotificationScheduler).cancelScheduledNotifications(account, device);
  }