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

import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.document.data.ResourceObject.Builder;
import com.airlock.iam.common.api.domain.model.otp.UnverifiedOtp;
import com.airlock.iam.common.application.jsonapi.ResourceIdentifierUtils;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed.BaseChoiceToken;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultData;
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.mtan.MtanOtpCheck;
import com.airlock.iam.flow.shared.domain.model.mtan.MtanOtpCheckingStep.MtanNumberChoices;
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.TransactionApprovalMtanTokenDataCollectionDocument;
import com.airlock.iam.transactionapproval.infrastructure.generated.transfer.response.TransactionApprovalResendMtanOtpPossibleDataResourceDocument;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalMtanTokenData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalResendMtanOtpPossibleData;
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.ExampleObject;
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.core.Response;
import ma.glasnost.orika.MapperFacade;

@Path("/transaction-approval/mtan")
public class MtanTransactionApprovalFlowResource {
   private static final String FLOW_RESULT_EXAMPLE_RESPONSE = "{\n  \"meta\": {\n    \"type\": \"jsonapi.metadata.document\",\n    \"timestamp\": \"2025-07-03T11:03:00.629+02:00\"\n  },\n  \"included\": [\n    {\n      \"type\": \"transaction-approval.mtan.otp.resend.information\",\n      \"id\": \"731854796\",\n      \"attributes\": {\n        \"otpResendPossible\": false\n      }\n    }\n  ],\n  \"data\": {\n    \"type\": \"transaction-approval.session\",\n    \"id\": \"572953137496921011\",\n    \"attributes\": {\n      \"nextStep\": \"MTAN_OTP_REQUIRED\",\n      \"phoneNumber\": \"+41761234567\",\n      \"resendPossible\": false\n    }\n  }\n}\n";
   private final FlowResultMapper mapper;
   private final MapperFacade mapperFacade;
   private final Provider<MtanOtpCheck> mtanAuthentication;

   @Inject
   public MtanTransactionApprovalFlowResource(FlowResultMapper mapper, MapperFacade mapperFacade, Provider<MtanOtpCheck> mtanAuthentication) {
      this.mapper = mapper;
      this.mapperFacade = mapperFacade;
      this.mtanAuthentication = mtanAuthentication;
   }

   @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>MTAN_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>MTAN_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>MTAN_OTP_WRONG</td>\n<td>OTP could not be validated successfully and no retries are allowed.</td>\n</tr>\n<tr>\n<td>MTAN_OTP_EXPIRED</td>\n<td>The OTP has expired.</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 = "Checks the submitted mTAN OTP.\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 = "SMS/mTAN",
      summary = "Check mTAN OTP"
   )
   public Response checkMtanOtp(@Parameter(description = "Request containing the mTAN OTP.") @NotNull @Valid GenericOtpCheckRequest request) {
      FlowResult result = ((MtanOtpCheck)this.mtanAuthentication.get()).checkOtp(UnverifiedOtp.unverifiedOtp(request.getOtp()));
      return this.mapper.toResponse(result, new ResourceObject[0]);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Available mTAN tokens retrieved.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = TransactionApprovalMtanTokenDataCollectionDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalMtanTokenDataCollectionDocument.class))
      }
   )
   @Deprecated
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/tokens/retrieve")
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The number choices are returned as\n<a href=\"#additionalTransactionApprovalAttributes\">additional attributes</a> with the <tt>MTAN_TOKEN_CHOICE_REQUIRED</tt> next step code.\n<p>Retrieves the set of active mTAN tokens.</p>\n<div class=\"iam-resource-return\">The collection of active mTAN tokens.</div>",
      tags = "SMS/mTAN",
      summary = "List tokens"
   )
   public Response retrieveMtanTokens() {
      FlowResult result = ((MtanOtpCheck)this.mtanAuthentication.get()).getNumberChoice();
      return FlowResultToCollectionDocumentMapper.flowResultDataToCollectionDocumentMapper(this.mapper, this.mapperFacade)
         .map(result, MtanNumberChoices.class, TransactionApprovalMtanTokenData.class, BaseChoiceToken::getId);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "mTAN token successfully selected, OTP sent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n  \"meta\": {\n    \"type\": \"jsonapi.metadata.document\",\n    \"timestamp\": \"2025-07-03T11:03:00.629+02:00\"\n  },\n  \"included\": [\n    {\n      \"type\": \"transaction-approval.mtan.otp.resend.information\",\n      \"id\": \"731854796\",\n      \"attributes\": {\n        \"otpResendPossible\": false\n      }\n    }\n  ],\n  \"data\": {\n    \"type\": \"transaction-approval.session\",\n    \"id\": \"572953137496921011\",\n    \"attributes\": {\n      \"nextStep\": \"MTAN_OTP_REQUIRED\",\n      \"phoneNumber\": \"+41761234567\",\n      \"resendPossible\": false\n    }\n  }\n}\n"
                        )
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n  \"meta\": {\n    \"type\": \"jsonapi.metadata.document\",\n    \"timestamp\": \"2025-07-03T11:03:00.629+02:00\"\n  },\n  \"included\": [\n    {\n      \"type\": \"transaction-approval.mtan.otp.resend.information\",\n      \"id\": \"731854796\",\n      \"attributes\": {\n        \"otpResendPossible\": false\n      }\n    }\n  ],\n  \"data\": {\n    \"type\": \"transaction-approval.session\",\n    \"id\": \"572953137496921011\",\n    \"attributes\": {\n      \"nextStep\": \"MTAN_OTP_REQUIRED\",\n      \"phoneNumber\": \"+41761234567\",\n      \"resendPossible\": false\n    }\n  }\n}\n"
                        )
                     )
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>MTAN_TOKEN_CHOICE_FAILED</td>\n<td>Invalid mTAN token ID. Retry with valid ID.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>MTAN_TOKEN_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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "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("/tokens/{id}/select")
   @Operation(
      description = "Selects one of the active mTAN tokens (phone numbers) for sending the OTP to.\n<div class=\"iam-resource-return\">Whether the token selection and sending of the OTP was successful. Gives information about the next transaction approval step required.\n Additionally, the response includes information about the possibility of an OTP resend.\n <p><b>Deprecated:</b> The 'included' section containing resend information is deprecated and will be removed in a future version.\n The resend information is returned as <a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a>\n with the <tt>MTAN_OTP_REQUIRED</tt> next step code.</p></div>",
      tags = "SMS/mTAN",
      summary = "Select token"
   )
   public Response selectMtanToken(@Parameter(description = "the ID of the selected mTAN token.") @PathParam("id") @Size(min = 1, max = 50) String id) {
      FlowResult selectResult = ((MtanOtpCheck)this.mtanAuthentication.get()).selectMtanToken(id);
      if (selectResult.isFailed()) {
         return this.mapper.toResponse(selectResult, new ResourceObject[0]);
      }

      FlowResult resendPossibleResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtpPossible();
      return resendPossibleResult.isFailed()
         ? this.mapper.toResponse(resendPossibleResult, new ResourceObject[0])
         : this.mapper.toResponse(selectResult, new ResourceObject[]{this.extractResourceObject(resendPossibleResult).build()});
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The OTP has been resent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n  \"meta\": {\n    \"type\": \"jsonapi.metadata.document\",\n    \"timestamp\": \"2025-07-03T11:03:00.629+02:00\"\n  },\n  \"included\": [\n    {\n      \"type\": \"transaction-approval.mtan.otp.resend.information\",\n      \"id\": \"731854796\",\n      \"attributes\": {\n        \"otpResendPossible\": false\n      }\n    }\n  ],\n  \"data\": {\n    \"type\": \"transaction-approval.session\",\n    \"id\": \"572953137496921011\",\n    \"attributes\": {\n      \"nextStep\": \"MTAN_OTP_REQUIRED\",\n      \"phoneNumber\": \"+41761234567\",\n      \"resendPossible\": false\n    }\n  }\n}\n"
                        )
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = TransactionApprovalFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n  \"meta\": {\n    \"type\": \"jsonapi.metadata.document\",\n    \"timestamp\": \"2025-07-03T11:03:00.629+02:00\"\n  },\n  \"included\": [\n    {\n      \"type\": \"transaction-approval.mtan.otp.resend.information\",\n      \"id\": \"731854796\",\n      \"attributes\": {\n        \"otpResendPossible\": false\n      }\n    }\n  ],\n  \"data\": {\n    \"type\": \"transaction-approval.session\",\n    \"id\": \"572953137496921011\",\n    \"attributes\": {\n      \"nextStep\": \"MTAN_OTP_REQUIRED\",\n      \"phoneNumber\": \"+41761234567\",\n      \"resendPossible\": false\n    }\n  }\n}\n"
                        )
                     )
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>MTAN_OTP_RESEND_REFUSED</td>\n<td>The OTP could not be resent.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>MTAN_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 = "Resend 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("/otp/resend")
   @Operation(
      description = "Resends the mTAN OTP by SMS.\n<div class=\"iam-resource-return\">Whether the resend was successful and informs about the <tt>nextStep</tt>.\n Additionally, the response includes information about the possibility of an OTP resend.\n <p><b>Deprecated:</b> The 'included' section containing resend information is deprecated and will be removed in a future version.\n The resend information is returned as <a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a>\n with the <tt>MTAN_OTP_REQUIRED</tt> next step code.</p></div>",
      tags = "SMS/mTAN",
      summary = "Resend OTP"
   )
   public Response resendMtanOtp() {
      FlowResult resendResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtp();
      if (resendResult.isFailed()) {
         return this.mapper.toResponse(resendResult, new ResourceObject[0]);
      }

      FlowResult resendPossibleResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtpPossible();
      return resendPossibleResult.isFailed()
         ? this.mapper.toResponse(resendPossibleResult, new ResourceObject[0])
         : this.mapper.toResponse(resendResult, new ResourceObject[]{this.extractResourceObject(resendPossibleResult).build()});
   }

   @ApiResponse(
      responseCode = "200",
      description = "Returns information about a possible OTP resend.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = TransactionApprovalResendMtanOtpPossibleDataResourceDocument.class)
            ),
            @Content(mediaType = "application/json", schema = @Schema(implementation = TransactionApprovalResendMtanOtpPossibleDataResourceDocument.class))
      }
   )
   @Deprecated
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/otp/resend-info/retrieve")
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The resend information is returned as\n<a href=\"#additionalTransactionApprovalAttributes\">additional attribute</a> with the <tt>MTAN_OTP_REQUIRED</tt> next step code.\n<p>Returns whether a new OTP can be resent by SMS.</p>\n<div class=\"iam-resource-return\">Whether an OTP resend is possible or not.</div>",
      tags = "SMS/mTAN",
      summary = "Verify OTP resend possible"
   )
   public Response retrieveResendInfo() {
      FlowResult resendPossibleResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtpPossible();
      return resendPossibleResult.isFailed()
         ? this.mapper.toResponse(resendPossibleResult, new ResourceObject[0])
         : Response.ok(this.extractResourceObject(resendPossibleResult).document()).build();
   }

   private Builder<TransactionApprovalResendMtanOtpPossibleData> extractResourceObject(FlowResult resendPossibleResult) {
      TransactionApprovalResendMtanOtpPossibleData data = (TransactionApprovalResendMtanOtpPossibleData)this.mapperFacade
         .map((StepResultData)resendPossibleResult.data().orElseThrow(), TransactionApprovalResendMtanOtpPossibleData.class);
      return ResourceObject.builder(ResourceIdentifierUtils.getIdFor(resendPossibleResult), data);
   }
}
