package com.airlock.iam.admin.infrastructure.resource.prot;

import ch.ergon.restal.jsonapi.document.data.Relationship;
import ch.ergon.restal.jsonapi.document.data.ResourceDocument;
import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.document.data.ResourceObject.Builder;
import ch.ergon.restal.jsonapi.document.request.RequestDocument;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.SmsDeliveryStatusDataResourceDocument;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.SmsSendConfirmationResourceDocument;
import com.airlock.iam.admin.infrastructure.transfer.request.SmsSendRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.SmsDeliveryStatusData;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.common.application.exception.RequestDocumentAttributesMissingException;
import com.airlock.iam.common.domain.model.sms.Sms;
import com.airlock.iam.common.domain.model.sms.SmsMessageId;
import com.airlock.iam.common.domain.service.sms.SmsService;
import com.airlock.iam.common.infrastructure.resource.Resources;
import com.airlock.iam.core.misc.sms.SmsGateway.DeliveryStatus;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.net.URI;
import java.util.Optional;
import ma.glasnost.orika.MapperFacade;

@Path("/sms")
public class SmsResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(SmsResource.class);
   private final Provider<Optional<SmsService>> smsService;
   private final MapperFacade mapper;
   private final Provider<ExternalUriInfo> uriInfo;

   @Inject
   public SmsResource(Provider<Optional<SmsService>> smsService, MapperFacade mapper, Provider<ExternalUriInfo> uriInfo) {
      this.smsService = smsService;
      this.mapper = mapper;
      this.uriInfo = uriInfo;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "201",
               description = "The SMS was sent successfully.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = SmsSendConfirmationResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = SmsSendConfirmationResourceDocument.class))
               }
            ),
            @ApiResponse(responseCode = "204", description = "The SMS was sent successfully, but no tracking message ID is available."),
            @ApiResponse(
               responseCode = "400",
               description = "The content of the send request is invalid (e.g. malformed phone number or originator).\nAn originator is only allowed and required if the configuration did not already define one.\n",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse")),
                     @Content(mediaType = "application/json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse"))
               }
            ),
            @ApiResponse(responseCode = "501", description = "The SMS service is not configured.")
      }
   )
   @POST
   @Operation(
      description = "Sends an SMS.\n<div class=\"iam-resource-return\">If the service can determine a message ID for the SMS being sent, the resulting\n document contains a relationship with key 'sms.status' which is a link that allows to\n retrieve the status of the SMS.</div>",
      tags = "SMS/mTAN",
      summary = "Send SMS"
   )
   @Consumes({"application/vnd.api+json", "application/json"})
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response sendSms(@Parameter(description = "The request for sending an SMS.") @NotNull @Valid RequestDocument<SmsSendRequest> smsSendRequestDocument) {
      SmsSendRequest attributes = (SmsSendRequest)RequestDocumentAttributesMissingException.checkAttributesPresent(smsSendRequestDocument);
      Sms sms = Sms.builder()
         .originator(attributes.getOriginator())
         .phoneNumber(attributes.getPhoneNumber())
         .message(attributes.getMessage())
         .flashMessage(attributes.getIsFlash())
         .build();
      if (!this.smsService().isValid(sms)) {
         LOG.info("The SMS content violates the requirements of the REST API and cannot be sent.");
         return ErrorResponse.badRequest().build().toResponse();
      }

      SmsMessageId id = this.smsService().send(sms);
      if (!id.exists()) {
         return Response.noContent().build();
      }

      Builder<Void> builder = ResourceObject.builder("sms.send.confirmation", id.getId());
      URI related = Resources.uri((ExternalUriInfo)this.uriInfo.get(), SmsResource.class).path(id.getId()).build(new Object[0]);
      builder.withRelationship("sms.status", Relationship.toOneRelated(related));
      ResourceDocument<Void> document = new ResourceDocument(builder.build());
      return Response.status(Status.CREATED).entity(document).build();
   }

   @ApiResponse(
      responseCode = "200",
      description = "On successful SMS delivery status retrieval.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = SmsDeliveryStatusDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = SmsDeliveryStatusDataResourceDocument.class))
      }
   )
   @GET
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("{messageId}")
   @Operation(
      description = "Retrieves the delivery status of an SMS.\n<div class=\"iam-resource-return\">The message's delivery status.</div>",
      tags = "SMS/mTAN",
      summary = "List SMS status"
   )
   public Response getDeliveryStatus(
      @Parameter(description = "The message ID for which to retrieve the message's status.") @PathParam("messageId") String messageId
   ) {
      SmsMessageId id = SmsMessageId.builder().id(Optional.ofNullable(messageId)).build();
      DeliveryStatus deliveryStatus = this.smsService().getDeliveryStatus(id);
      SmsDeliveryStatusData status = (SmsDeliveryStatusData)this.mapper.map(deliveryStatus, SmsDeliveryStatusData.class);
      ResourceDocument<SmsDeliveryStatusData> document = ResourceObject.builder(messageId, status).document();
      return Response.ok(document).build();
   }

   private SmsService smsService() {
      return (SmsService)ServiceConfigurationException.checkPresent((Optional)this.smsService.get(), "SMS Service is not configured");
   }
}
