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

import com.airlock.iam.authentication.infrastructure.generated.transfer.response.AuthenticationCrontoPushDeviceDataCollectionDocument;
import com.airlock.iam.authentication.infrastructure.generated.transfer.response.CrontoChallengeDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.transfer.response.AuthenticationCrontoPushDeviceData;
import com.airlock.iam.authentication.infrastructure.transfer.response.CrontoChallengeData;
import com.airlock.iam.core.domain.model.cronto.CrontoOtp;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.infrastructure.mapper.FlowResultMapper;
import com.airlock.iam.flow.auth.api.infrastructure.generated.transfer.response.AuthenticationFlowResultDataResourceDocument;
import com.airlock.iam.flow.infrastructure.mapper.FlowResultToCollectionDocumentMapper;
import com.airlock.iam.flow.shared.application.service.cronto.CrontoAuthentication;
import com.airlock.iam.flow.shared.domain.model.cronto.CrontoPushDeviceChoices;
import com.airlock.iam.flow.shared.infrastructure.transfer.request.GenericOtpCheckRequest;
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("/cronto")
public class CrontoAuthenticationFlowResource {
   private final FlowResultMapper mapper;
   private final MapperFacade mapperFacade;
   private final Provider<CrontoAuthentication> crontoAuthentication;

   @Inject
   public CrontoAuthenticationFlowResource(FlowResultMapper mapper, MapperFacade mapperFacade, Provider<CrontoAuthentication> crontoAuthentication) {
      this.mapper = mapper;
      this.mapperFacade = mapperFacade;
      this.crontoAuthentication = crontoAuthentication;
   }

   @ApiResponse(
      responseCode = "200",
      description = "A challenge has been returned.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = CrontoChallengeDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = CrontoChallengeDataResourceDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The challenge information is returned as\n<a href=\"#additionalAuthAttributes\">additional attribute</a> with the <tt>CRONTO_OTP_REQUIRED</tt> next step code.\n<p>Returns a challenge for Cronto authentication.</p>\n<p>The challenge is delivered as a base64-encoded image ('cryptogram') and as a string representation for\napp-to-app authentication.</p>\n<p>\nThe two attributes \"onlineValidation\" and \"pushed\" have only informative character and indicate\nthe recommended behavior for clients:\n<ul>\n<li>If the \"onlineValidation\" attribute is true, the client application should not ask the user for the\nresponse OTP, as the app will send the response directly to the server (Scan&amp;Login or Push use-cases).\nInstead, the client application should regularly poll (<tt>POST /cronto/otp/poll</tt>) to check if a response\nhas been received. There should be an option for the user to fall back to entering the OTP manually. Sending the\nmanually entered OTP to (<tt>POST /cronto/otp/check</tt>) cancels the online validation</li>\n<li>If also the \"pushed\" attribute is true, the challenge has been pushed to a Cronto app. In this case also\nthe cryptogram should not be displayed. Instead, the user should be informed about this and asked to confirm\nthe transaction on their Cronto app. Again, a fallback for offline situations should be available.\nIf \"pushed\" is true, \"onlineValidation\" is always also true.</li>\n</ul>\n</p>\n<div class=\"iam-resource-return\">A Cronto challenge.</div>",
      tags = "Cronto",
      summary = "Get challenge"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/challenge/retrieve")
   public Response retrieveChallenge() {
      FlowResult result = ((CrontoAuthentication)this.crontoAuthentication.get()).getCurrentChallenge();
      return this.mapper.toResponseWithData(result, CrontoChallengeData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Authentication successful or further steps required.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>CRONTO_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>CRONTO_OTP_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>CRONTO_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 authentication 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 Cronto OTP.\nThe OTP was entered by the user (after scanning the cryptogram image) or returned by the Cronto app\n(when using the secure channel challenge in app-to-app communication).\n<p>Use this call for manual OTP validation for a <tt>CRONTO_OTP_REQUIRED</tt> next auth step\n(when \"onlineValidation\" was false, or offline fallback was selected by the user). Note that in any case\nmanually OTP checking <em>cancels online validation</em>. It is recommended to obtain the current\nchallenge again after each failed OTP check, as the \"onlineValidation\" flag could have changed.</p>\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 authenticate. If the attribute is missing, the user is\n successfully authenticated.</div>",
      tags = "Cronto",
      summary = "Verify Cronto OTP"
   )
   @POST
   @Path("/otp/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkCrontoOtp(@Parameter(description = "Contains the Cronto OTP.") @NotNull @Valid GenericOtpCheckRequest request) {
      FlowResult result = ((CrontoAuthentication)this.crontoAuthentication.get()).checkOtp(new CrontoOtp(request.getOtp()));
      return this.mapper.toResponse(result);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Challenge not yet validated (retry later or check OTP manually), authentication successful or further steps required.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>CRONTO_OTP_WRONG</td>\n<td>The OTP could not be validated successfully.</td>\n</tr>\n<tr>\n<td>CANCELLED_BY_USER</td>\n<td>The process has been cancelled by the user in the app.</td>\n</tr>\n</table>\n</div>\n<br>The current authentication 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 = "Allows polling whether the Cronto challenge has been answered by the app (\"online validation\"). If there\nhas not been any response from the app, keep polling in regular intervals or check the OTP manually\n(<tt>POST /cronto/otp/check</tt>).\n<div class=\"iam-resource-return\">The status of the online validation (still waiting/success/cancelled/failed).\n If the attribute <tt>nextAuthStep</tt> is present in the response, further steps are required to\n successfully authenticate. If the attribute is missing, the user is successfully authenticated.</div>",
      tags = "Cronto",
      summary = "Poll status"
   )
   @POST
   @Path("/otp/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 = ((CrontoAuthentication)this.crontoAuthentication.get()).pollOnlineValidation(queryOnly);
      return this.mapper.toResponse(result);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Available push devices retrieved.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = AuthenticationCrontoPushDeviceDataCollectionDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = AuthenticationCrontoPushDeviceDataCollectionDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The device choices are returned as\n<a href=\"#additionalAuthAttributes\">additional attributes</a> with the <tt>CRONTO_DEVICE_CHOICE_REQUIRED</tt> next step code.\n<p>Retrieves the set of active Cronto push devices.</p>\n<div class=\"iam-resource-return\">The collection of active Cronto push devices</div>",
      tags = "Cronto",
      summary = "List devices"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/push-devices/retrieve")
   public Response retrievePushDeviceChoices() {
      FlowResult result = ((CrontoAuthentication)this.crontoAuthentication.get()).getPushDeviceChoices();
      return FlowResultToCollectionDocumentMapper.flowResultDataToCollectionDocumentMapper(this.mapper, this.mapperFacade)
         .map(result, CrontoPushDeviceChoices.class, AuthenticationCrontoPushDeviceData.class, device -> device.getId());
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Successfully selected an offline device.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Offline selection failed. The current authentication 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 = "Selects offline authentication. No push notification is sent to a device and no subsequent online validation\nof the response will be possible.\n<div class=\"iam-resource-return\">Whether the selection of offline authentication was successful. Gives information about the next authentication step required.</div>",
      tags = "Cronto",
      summary = "Select offline auth"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/offline/")
   public Response selectOfflineAuthentication() {
      FlowResult result = ((CrontoAuthentication)this.crontoAuthentication.get()).selectOfflineAuthentication();
      return this.mapper.toResponse(result);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Push device successfully selected, notification sent.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>CRONTO_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>CRONTO_DEVICE_CHOICE_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>CRONTO_DEVICE_CHOICE_FAILED</td>\n<td>Invalid ID selected.</td>\n</tr>\n</table>\n</div>\n<br>The current authentication 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 = "Selects one of the active push devices to which the push notification will be sent.\n<div class=\"iam-resource-return\">Whether the device selection was successful. Gives information about the next authentication step required.</div>",
      tags = "Cronto",
      summary = "Select push device"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/push-devices/{id}/select")
   public Response selectPushDevice(@Parameter(description = "The ID of the selected push device.") @PathParam("id") @Size(min = 1, max = 50) String id) {
      FlowResult result = ((CrontoAuthentication)this.crontoAuthentication.get()).selectPushDevice(id);
      return this.mapper.toResponse(result);
   }
}
