/*
 * Copyright 2023 Signal Messenger, LLC
 * SPDX-License-Identifier: AGPL-3.0-only
 */

package org.whispersystems.textsecuregcm.mappers;

import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.jersey.errors.ErrorMessage;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import java.util.Map;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionChargeFailurePaymentRequiredException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionException;
import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionForbiddenException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionInvalidAmountException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionInvalidArgumentsException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionNotFoundException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionPaymentRequiredException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorConflictException;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorException;

public class SubscriptionExceptionMapper implements ExceptionMapper<SubscriptionException> {
  @VisibleForTesting
  public static final int PROCESSOR_ERROR_STATUS_CODE = 440;

  public record ChargeFailureResponse(String processor, ChargeFailure chargeFailure) {}

  @Override
  public Response toResponse(final SubscriptionException exception) {

    // Some exceptions have specific error body formats
    if (exception instanceof SubscriptionInvalidAmountException e) {
      return Response
          .status(Response.Status.BAD_REQUEST)
          .entity(Map.of("error", e.getErrorCode()))
          .type(MediaType.APPLICATION_JSON_TYPE)
          .build();
    }
    if (exception instanceof SubscriptionProcessorException e) {
      return Response.status(PROCESSOR_ERROR_STATUS_CODE)
          .entity(new ChargeFailureResponse(e.getProcessor().name(), e.getChargeFailure()))
          .type(MediaType.APPLICATION_JSON_TYPE)
          .build();
    }
    if (exception instanceof SubscriptionChargeFailurePaymentRequiredException e) {
      return Response
          .status(Response.Status.PAYMENT_REQUIRED)
          .entity(new ChargeFailureResponse(e.getProcessor().name(), e.getChargeFailure()))
          .type(MediaType.APPLICATION_JSON_TYPE)
          .build();
    }

    // Otherwise, we'll return a generic error message WebApplicationException, with a detailed error if one is provided
    final Response.Status status = (switch (exception) {
      case SubscriptionNotFoundException e -> Response.Status.NOT_FOUND;
      case SubscriptionForbiddenException e -> Response.Status.FORBIDDEN;
      case SubscriptionInvalidArgumentsException e -> Response.Status.BAD_REQUEST;
      case SubscriptionProcessorConflictException e -> Response.Status.CONFLICT;
      case SubscriptionPaymentRequiredException e -> Response.Status.PAYMENT_REQUIRED;
      default -> Response.Status.INTERNAL_SERVER_ERROR;
    });

    // If the SubscriptionException came with suitable error message, include that in the response body. Otherwise,
    // don't provide any message to the WebApplicationException constructor so the response includes the default
    // HTTP error message for the status.
    final WebApplicationException wae = exception.errorDetail()
        .map(errorMessage -> new WebApplicationException(errorMessage, exception, Response.status(status).build()))
        .orElseGet(() -> new WebApplicationException(exception, Response.status(status).build()));

    return Response
        .fromResponse(wae.getResponse())
        .type(MediaType.APPLICATION_JSON_TYPE)
        .entity(new ErrorMessage(wae.getResponse().getStatus(), wae.getLocalizedMessage())).build();

  }
}
