@Override
  public CompletableFuture<SendPushNotificationResult> sendNotification(final PushNotification notification) {
    final String topic = switch (notification.tokenType()) {
      case APN -> bundleId;
      case APN_VOIP -> bundleId + ".voip";
      default -> throw new IllegalArgumentException("Unsupported token type: " + notification.tokenType());
    };

    final boolean isVoip = notification.tokenType() == PushNotification.TokenType.APN_VOIP;

    final String payload = switch (notification.notificationType()) {
      case NOTIFICATION -> {
        if (isVoip) {
          yield APN_VOIP_NOTIFICATION_PAYLOAD;
        } else {
          yield notification.urgent() ? APN_NSE_NOTIFICATION_PAYLOAD : APN_BACKGROUND_PAYLOAD;
        }
      }

      case ATTEMPT_LOGIN_NOTIFICATION_HIGH_PRIORITY -> new SimpleApnsPayloadBuilder()
          .setMutableContent(true)
          .setLocalizedAlertMessage("APN_Message")
          .addCustomProperty("attemptLoginContext", notification.data())
          .build();

      case CHALLENGE -> new SimpleApnsPayloadBuilder()
          .setContentAvailable(true)
          .addCustomProperty("challenge", notification.data())
          .build();

      case RATE_LIMIT_CHALLENGE -> new SimpleApnsPayloadBuilder()
          .setContentAvailable(true)
          .addCustomProperty("rateLimitChallenge", notification.data())
          .build();
    };

    final PushType pushType = switch (notification.notificationType()) {
      case NOTIFICATION -> {
        if (isVoip) {
          yield PushType.VOIP;
        } else {
          yield notification.urgent() ? PushType.ALERT : PushType.BACKGROUND;
        }
      }
      case ATTEMPT_LOGIN_NOTIFICATION_HIGH_PRIORITY -> PushType.ALERT;
      case CHALLENGE, RATE_LIMIT_CHALLENGE -> PushType.BACKGROUND;
    };

    final DeliveryPriority deliveryPriority;

    if (pushType == PushType.BACKGROUND) {
      deliveryPriority = DeliveryPriority.CONSERVE_POWER;
    } else {
      deliveryPriority = (notification.urgent() || isVoip)
          ? DeliveryPriority.IMMEDIATE
          : DeliveryPriority.CONSERVE_POWER;
    }

    final String collapseId =
        (notification.notificationType() == PushNotification.NotificationType.NOTIFICATION && notification.urgent() && !isVoip)
            ? "incoming-message" : null;

    final Instant start = Instant.now();

    return apnsClient.sendNotification(new SimpleApnsPushNotification(notification.deviceToken(),
        topic,
        payload,
        MAX_EXPIRATION,
        deliveryPriority,
        pushType,
        collapseId))
        .whenComplete((response, throwable) -> {
          // Note that we deliberately run this small bit of non-blocking measurement on the "send notification" thread
          // to avoid any measurement noise that could arise from dispatching to another executor and waiting in its
          // queue
          SEND_NOTIFICATION_TIMER.record(Duration.between(start, Instant.now()));
        })
        .thenApplyAsync(response -> {
          final boolean accepted;
          final Optional<String> rejectionReason;
          final boolean unregistered;

          if (response.isAccepted()) {
            accepted = true;
            rejectionReason = Optional.empty();
            unregistered = false;
          } else {
            accepted = false;
            rejectionReason = response.getRejectionReason();
            unregistered = response.getRejectionReason().map(reason -> "Unregistered".equals(reason) || "BadDeviceToken".equals(reason) || "ExpiredToken".equals(reason))
                .orElse(false);
          }

          return new SendPushNotificationResult(accepted, rejectionReason, unregistered, response.getTokenInvalidationTimestamp());
        }, executor);
  }
@Test
  void testFailure() {
    PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
    when(response.isAccepted()).thenReturn(true);

    when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
        .thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0),
            new IOException("lost connection")));

    PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN_VOIP,
        PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, true);

    assertThatThrownBy(() -> apnSender.sendNotification(pushNotification).join())
        .isInstanceOf(CompletionException.class)
        .hasCauseInstanceOf(IOException.class);

    verify(apnsClient).sendNotification(any());

    verifyNoMoreInteractions(apnsClient);
  }