package com.airlock.iam.transactionapproval.infrastructure.resource.airlock2fa;

import ch.ergon.restal.jsonapi.document.data.ResourceObject;
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.transactionapproval.api.infrastructure.generated.transfer.response.TransactionApprovalFlowResultDataResourceDocument;
import com.airlock.iam.transactionapproval.infrastructure.generated.transfer.response.TransactionApprovalAirlock2FADeviceDataCollectionDocument;
import com.airlock.iam.transactionapproval.infrastructure.generated.transfer.response.TransactionApprovalAirlock2FAMobileOnlyChallengeDataResourceDocument;
import com.airlock.iam.transactionapproval.infrastructure.generated.transfer.response.TransactionApprovalAirlock2FAOfflineChallengeDataResourceDocument;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalAirlock2FADeviceData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalAirlock2FAMobileOnlyChallengeData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalAirlock2FAOfflineChallengeData;
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("/transaction-approval/airlock-2fa")
public class Airlock2FATransactionApprovalFlowResource {
   private final Provider<Airlock2FAApproval> messageApproval;
   private final FlowResultMapper mapper;
   private final MapperFacade mapperFacade;

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

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Transaction approval successful (if attribute <tt>nextStep</tt> is missing),\ntransaction not yet validated (if <tt>nextStep</tt> has value <tt>AIRLOCK_2FA_POLLING_REQUIRED</tt> or <tt>AIRLOCK_2FA_POLLING_OR_OTP_REQUIRED</tt>),\nfurther steps required in all other cases.\n",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>TRANSACTION_DENIED</td>\n<td>The transaction was denied. This could be due to the user actively cancelling the transaction or a timeout.</td>\n</tr>\n</table>\n</div>\n<br>The current transaction approval 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"))
               }
            )
      }
   )
   @POST
   @Path("/status/poll")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Operation(
      description = "<p>Polls Airlock 2FA transaction approval status.</p>\n<p>Allows polling whether the transaction 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.\n If the attribute <tt>nextStep</tt> is present in the response, further steps are required\n to successfully approve the transaction. If the attribute is missing, the transaction approval\n of the user was successful.</div>",
      tags = "Airlock 2FA",
      summary = "Poll status"
   )
   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, new ResourceObject[0]);
   }

   @ApiResponse(
      responseCode = "200",
      description = "A challenge has been returned.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = TransactionApprovalAirlock2FAOfflineChallengeDataResourceDocument.class)
            ),
            @Content(
               mediaType = "application/json",
               schema = @Schema(implementation = TransactionApprovalAirlock2FAOfflineChallengeDataResourceDocument.class)
            )
      }
   )
   @Deprecated
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/challenge/retrieve")
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The challenge is returned as <a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a> with the <tt>AIRLOCK_2FA_OTP_REQUIRED</tt>\nnext 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"
   )
   public Response retrieveOfflineQrCodeChallenge() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).retrieveOfflineQrCode();
      return this.mapper.toResponseWithData(result, TransactionApprovalAirlock2FAOfflineChallengeData.class);
   }

   @ApiResponse(
      responseCode = "200",
      description = "A challenge has been returned.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = TransactionApprovalAirlock2FAMobileOnlyChallengeDataResourceDocument.class)
            ),
            @Content(
               mediaType = "application/json",
               schema = @Schema(implementation = TransactionApprovalAirlock2FAMobileOnlyChallengeDataResourceDocument.class)
            )
      }
   )
   @Deprecated
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/mobile-only/challenge/retrieve/")
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The mobile authentication URI is returned as <a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a> with the\n<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 transaction 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"
   )
   public Response retrieveMobileOnlyChallenge() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).retrieveMobileOnlyChallenge();
      return this.mapper.toResponseWithData(result, TransactionApprovalAirlock2FAMobileOnlyChallengeData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Transaction approval successful or further steps required.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AIRLOCK_2FA_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_OTP_REQUIRED</tt> (as documented <a href=\"#nextApprovalStepCodes\">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_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 transaction approval 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"))
               }
            )
      }
   )
   @POST
   @Path("/otp/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @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_OTP_REQUIRED</tt> next approval step.\n</p>\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 transaction. If the attribute is missing, the transaction approval\n of the user was successful.</div>",
      tags = "Airlock 2FA",
      summary = "Verify OTP"
   )
   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, new ResourceObject[0]);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Successfully switched to offline transaction approval.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Offline selection failed. The current transaction approval 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"))
               }
            )
      }
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/offline/")
   @Operation(
      description = "Switch to Airlock 2FA offline transaction approval.\nThis will terminate any occurring online validation. As a consequence, any push notification sent can no longer be used to approve the transaction\nand subsequent online validation of the transaction is no longer possible.\nIf no validation was currently taking place when the call was made (because a device selection was required),\na new transaction 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 transaction approval was successful. Gives information about the next transaction approval step required.</div>",
      tags = "Airlock 2FA",
      summary = "Switch to offline"
   )
   public Response switchToOffline() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).switchToOffline();
      return this.mapper.toResponse(result, new ResourceObject[0]);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Available devices retrieved.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = TransactionApprovalAirlock2FADeviceDataCollectionDocument.class)
            ),
            @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalAirlock2FADeviceDataCollectionDocument.class))
      }
   )
   @Deprecated
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/devices/retrieve")
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The device choices are returned as <a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a> with the\n<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"
   )
   public Response retrieveDeviceChoices() {
      FlowResult result = ((Airlock2FAApproval)this.messageApproval.get()).listDevices();
      return result.isFailed() ? this.mapper.toResponse(result, new ResourceObject[0]) : this.devicesDataFrom(result);
   }

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

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Airlock 2FA device successfully selected, notification sent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.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=\"#nextApprovalStepCodes\">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"))
               }
            )
      }
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/devices/{id}/select")
   @Operation(
      description = "Selects one of the available Airlock 2FA devices to use for transaction approval.\n<div class=\"iam-resource-return\">Whether the device selection was successful. Gives information about the next transaction approval step required.</div>",
      tags = "Airlock 2FA",
      summary = "Select device"
   )
   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, new ResourceObject[0]);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Approval successful or further steps required.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.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 transaction approval 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 /transaction-approval/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, new ResourceObject[0]);
   }
}
