package com.airlock.iam.publicselfservice.infrastructure.resource;

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.factor.domain.model.airlock2fa.otp.Airlock2FAOtp;
import com.airlock.iam.factor.domain.model.airlock2fa.otp.Airlock2FAPasscode;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.infrastructure.mapper.FlowResultMapper;
import com.airlock.iam.flow.infrastructure.mapper.FlowResultToCollectionDocumentMapper;
import com.airlock.iam.flow.shared.application.service.airlock2fa.Airlock2FAApproval;
import com.airlock.iam.flow.shared.domain.model.airlock2fa.Airlock2FADeviceListData;
import com.airlock.iam.flow.shared.infrastructure.transfer.request.Airlock2FAPasscodeCheckRequest;
import com.airlock.iam.flow.shared.infrastructure.transfer.request.GenericOtpCheckRequest;
import com.airlock.iam.publicselfservice.api.infrastructure.generated.transfer.response.PublicSelfServiceFlowResultDataResourceDocument;
import com.airlock.iam.publicselfservice.infrastructure.generated.transfer.PublicSelfServiceAirlock2FADeviceDataCollectionDocument;
import com.airlock.iam.publicselfservice.infrastructure.generated.transfer.PublicSelfServiceAirlock2FAMobileOnlyChallengeDataResourceDocument;
import com.airlock.iam.publicselfservice.infrastructure.generated.transfer.PublicSelfServiceAirlock2FAOfflineChallengeDataResourceDocument;
import com.airlock.iam.publicselfservice.infrastructure.transfer.PublicSelfServiceAirlock2FADeviceData;
import com.airlock.iam.publicselfservice.infrastructure.transfer.PublicSelfServiceAirlock2FAMobileOnlyChallengeData;
import com.airlock.iam.publicselfservice.infrastructure.transfer.PublicSelfServiceAirlock2FAOfflineChallengeData;
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.validation.constraints.Size;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response;
import ma.glasnost.orika.MapperFacade;

@Path("/approval/airlock-2fa")
public class PublicSelfServiceAirlock2FAApprovalResource {
   private final Provider<Airlock2FAApproval> messageApproval;
   private final FlowResultMapper mapper;
   private final MapperFacade mapperFacade;

   @Inject
   public PublicSelfServiceAirlock2FAApprovalResource(
      @NotNull Provider<Airlock2FAApproval> messageApproval, @NotNull FlowResultMapper mapper, @NotNull MapperFacade mapperFacade
   ) {
      this.messageApproval = messageApproval;
      this.mapper = mapper;
      this.mapperFacade = mapperFacade;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Airlock 2FA approval successful (if attribute <tt>nextStep</tt> is missing),\nmessage not yet approved (if <tt>nextStep</tt> has value <tt>AIRLOCK_2FA_POLLING_REQUIRED</tt> or <tt>AIRLOCK_2FA_POLLING_OR_OFFLINE_REQUIRED</tt>),\nfurther steps required in all other cases.\n",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>APPROVAL_FAILED</td>\n<td>The message was not approved. This could be due to the user actively cancelling on the app or a timeout.</td>\n</tr>\n</table>\n</div>\n<br>The current public self-service flow has been aborted.\n<br>Possible next step: none.\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"))
               }
            )
      }
   )
   @Operation(
      description = "<p>Polls Airlock 2FA approval status.</p>\n<p>Allows polling whether the message was approved by the app (\"online validation\"). If there\nhas not been any response from the app, keep polling in regular intervals or switch to\noffline mode (<tt>POST /airlock-2fa/offline</tt>), if allowed by the configuration, and check the OTP manually\n(<tt>POST /airlock-2fa/otp/check</tt>).</p>\n<p>This endpoint is used for authentication using the Airlock 2FA factors One-Touch and Online QR Code, or for mobile-only authentication.</p>\n<div class=\"iam-resource-return\">The status of the online validation. If the attribute <tt>nextStep</tt> is present in the response,\n further steps are required to successfully approve the message. If the attribute is missing,\n the approval was successful.</div>",
      tags = "Airlock 2FA",
      summary = "Poll status"
   )
   @POST
   @Path("/status/poll")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response pollOnlineValidation(
      @Parameter(
   description = "Whether the final result should be returned (optional, default <tt>false</tt>). See <a href=\"#pollingEndpoints\">polling endpoints</a> for details."
) @QueryParam("queryOnly") boolean queryOnly
   ) {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).pollOnlineValidation(queryOnly);
      return this.mapper.toResponse(result);
   }

   @ApiResponse(
      responseCode = "200",
      description = "A challenge has been returned.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = PublicSelfServiceAirlock2FAOfflineChallengeDataResourceDocument.class)
            ),
            @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceAirlock2FAOfflineChallengeDataResourceDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The challenge is returned as <a href=\"#additionalPublicSelfServiceAttributes\">additional attribute</a>\nwith the <tt>AIRLOCK_2FA_QR_CODE_OTP_REQUIRED</tt> next step code.\n<p>Returns the Offline QR Code challenge for Airlock 2FA.</p>\n<p>The challenge is delivered as a base64-encoded image.\nThe OTP returned by the user after scanning the image needs to be checked manually (<tt>POST /airlock-2fa/otp/check</tt>).</p>\n<div class=\"iam-resource-return\">A challenge for Airlock 2FA.</div>",
      tags = "Airlock 2FA",
      summary = "Get QR challenge"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/offline-qr-code/challenge/retrieve")
   public Response retrieveOfflineQrCodeChallenge() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).retrieveOfflineQrCode();
      return this.mapper.toResponseWithData(result, PublicSelfServiceAirlock2FAOfflineChallengeData.class);
   }

   @ApiResponse(
      responseCode = "200",
      description = "A challenge has been returned.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = PublicSelfServiceAirlock2FAMobileOnlyChallengeDataResourceDocument.class)
            ),
            @Content(
               mediaType = "application/json",
               schema = @Schema(implementation = PublicSelfServiceAirlock2FAMobileOnlyChallengeDataResourceDocument.class)
            )
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The mobile authentication URI is returned as <a href=\"#additionalPublicSelfServiceAttributes\">additional attribute</a>\nwith the <tt><s>AIRLOCK_2FA_MOBILE_ONLY_CHALLENGE_RETRIEVAL_REQUIRED</s></tt> or <tt>AIRLOCK_2FA_POLLING_REQUIRED</tt> next step codes.\n<p>Retrieves the challenge for message approval in case of mobile-only.</p>\n<div class=\"iam-resource-return\">a challenge in the form of a URI.</div>",
      tags = "Airlock 2FA",
      summary = "Get mobile-only challenge"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/mobile-only/challenge/retrieve/")
   public Response retrieveMobileOnlyChallenge() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).retrieveMobileOnlyChallenge();
      return this.mapper.toResponseWithData(result, PublicSelfServiceAirlock2FAMobileOnlyChallengeData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Airlock 2FA approval successful.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_QR_CODE_OTP_WRONG</td>\n<td>OTP could not be validated successfully. Retry with correct OTP.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>AIRLOCK_2FA_QR_CODE_OTP_REQUIRED</tt> (as documented <a href=\"#nextPublicSelfServiceStepCodes\">here</a>).\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 = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_QR_CODE_OTP_WRONG</td>\n<td>The OTP could not be validated successfully and no retries are allowed.</td>\n</tr>\n</table>\n</div>\n<br>The current public self-service flow has been aborted.\n<br>Possible next step: none.\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"))
               }
            )
      }
   )
   @Operation(
      description = "Verifies the provided OTP to validate an Airlock 2FA Offline QR Code challenge.\nThe OTP is provided by the user (after scanning the Offline QR Code challenge).\n<p>Use this call for manual OTP validation for a <tt>AIRLOCK_2FA_QR_CODE_OTP_REQUIRED</tt> next approval step.</p>\nNote that unlike identity verification, approval REST calls require an existing user and cannot prevent username enumeration.\n<div class=\"iam-resource-return\">Whether the check was successful. If the attribute <tt>nextStep</tt> is present in the response,\n further steps are required to successfully approve the message. If the attribute is missing,\n the approval was successful.</div>",
      tags = "Airlock 2FA",
      summary = "Verify OTP"
   )
   @POST
   @Path("/offline-qr-code/otp/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response validateOfflineQrCode(@Parameter(description = "Contains the Airlock 2FA OTP.") @NotNull @Valid GenericOtpCheckRequest request) {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).checkOtp(Airlock2FAOtp.airlock2FAOtp(request.getOtp()));
      return this.mapper.toResponse(result);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Successfully switched to offline message approval.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Offline selection failed. The current public self-service flow has been aborted.",
               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"))
               }
            )
      }
   )
   @Operation(
      description = "Switch to Airlock 2FA offline approval.\nThis will terminate any occurring online validation. As a consequence, any push notification sent can no longer be used to approve the message\nand subsequent online validation of the message is no longer possible.\nIf no validation was currently taking place when the call was made (because a device selection was required),\na new message approval process with Offline QR Code (only) will be transparently started.\n<p>This call is <em>required</em> to verify an OTP to validate an Offline QR Code challenge (<tt>POST /airlock-2fa/otp/check</tt>).\n</p>\n<div class=\"iam-resource-return\">Whether the switch to offline message approval was successful. Gives information about the next public self-service step required.</div>",
      tags = "Airlock 2FA",
      summary = "Switch to offline"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/offline/")
   public Response switchToOffline() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).switchToOffline();
      return this.mapper.toResponse(result);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Available devices retrieved.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceAirlock2FADeviceDataCollectionDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceAirlock2FADeviceDataCollectionDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The device choices are returned as <a href=\"#additionalPublicSelfServiceAttributes\">additional attribute</a>\nwith the <tt>AIRLOCK_2FA_DEVICE_CHOICE_REQUIRED</tt> next step code.\n<p>Retrieves all applicable Airlock 2FA devices.</p>\n<div class=\"iam-resource-return\">The collection of applicable Airlock 2FA devices</div>",
      tags = "Airlock 2FA",
      summary = "List devices"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/devices/retrieve")
   public Response retrieveDeviceChoices() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).listDevices();
      return result.isFailed() ? this.mapper.toResponse(result) : this.devicesDataFrom(result);
   }

   private Response devicesDataFrom(FlowResult result) {
      return FlowResultToCollectionDocumentMapper.flowResultDataToCollectionDocumentMapper(this.mapper, this.mapperFacade)
         .map(result, Airlock2FADeviceListData.class, PublicSelfServiceAirlock2FADeviceData.class, device -> device.getId().getId());
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Airlock 2FA device successfully selected. Further steps required, such as polling.\n<br>Possible next steps: <tt>AIRLOCK_2FA_POLLING_REQUIRED</tt>, <tt>AIRLOCK_2FA_POLLING_OR_OFFLINE_REQUIRED</tt> (as documented <a href=\"#nextPublicSelfServiceStepCodes\">here</a>).\n",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_DEVICE_CHOICE_FAILED</td>\n<td>Invalid ID selected. Retry with valid ID.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>AIRLOCK_2FA_DEVICE_CHOICE_REQUIRED</tt> (as documented <a href=\"#nextPublicSelfServiceStepCodes\">here</a>).\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"))
               }
            )
      }
   )
   @Operation(
      description = "Selects one of the available Airlock 2FA devices to use for the approval.\n<div class=\"iam-resource-return\">Whether the device selection was successful. Gives information about the next public self-service step required.</div>",
      tags = "Airlock 2FA",
      summary = "Select device"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/devices/{id}/select")
   public Response selectDevice(@Parameter(description = "The ID of the selected Airlock 2FA device.") @PathParam("id") @Size(min = 1, max = 50) String id) {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).selectDevice(Airlock2FADeviceId.airlock2FADeviceId(id));
      return this.mapper.toResponse(result);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Approval successful or further steps required.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_PASSCODE_WRONG</td>\n<td>Passcode could not be validated successfully. Retry with correct passcode.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>AIRLOCK_2FA_PASSCODE_REQUIRED</tt> (as documented <a href=\"#nextAuthStepCodes\">here</a>).\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 = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_PASSCODE_WRONG</td>\n<td>The passcode could not be validated successfully and no retries are allowed.</td>\n</tr>\n<tr>\n<td>AUTH_METHOD_INACTIVE</td>\n<td>The Airlock 2FA account is locked. No retries are allowed.</td>\n</tr>\n</table>\n</div>\n<br>The current public self-service flow has been aborted.\n<br>Possible next step: none.\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"))
               }
            )
      }
   )
   @Operation(
      description = "Verifies the submitted Airlock 2FA passcode. The previously selected device has no impact on the passcode verification.\nNote that passcode checks are only possible if the step is in offline mode (using <tt>POST /protected/self-service/airlock-2fa/offline/</tt>).\n<div class=\"iam-resource-return\">Whether the check was successful. If the attribute <tt>nextAuthStep</tt> is present in the response,\n further steps are required to successfully approve. If the attribute is missing, the message is\n successfully approved.</div>",
      tags = "Airlock 2FA",
      summary = "Verify passcode"
   )
   @POST
   @Path("/passcode/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkPasscode(@Parameter(description = "Contains the passcode.") @NotNull @Valid Airlock2FAPasscodeCheckRequest request) {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).checkPasscode(Airlock2FAPasscode.airlock2FAPasscode(request.getPasscode()));
      return this.mapper.toResponse(result);
   }
}
