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

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;
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;
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.PublicSelfServiceMtanTokenDataCollectionDocument;
import com.airlock.iam.publicselfservice.infrastructure.generated.transfer.PublicSelfServiceResendMtanOtpPossibleDataResourceDocument;
import com.airlock.iam.publicselfservice.infrastructure.transfer.PublicSelfServiceMtanTokenData;
import com.airlock.iam.publicselfservice.infrastructure.transfer.PublicSelfServiceResendMtanOtpPossibleData;
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("/approval/mtan")
public class PublicSelfServiceMtanApprovalResource {
   private static final String FLOW_RESULT_EXAMPLE_RESPONSE = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:11:42.240+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"public-self-service.approval.mtan.otp.resend.information\",\n\t\t\"id\": \"797271294\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"public-self-service.session\",\n\t  \"id\": \"516534791446228062\",\n\t  \"attributes\": {\n\t\t\"nextAuthStep\": \"MTAN_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41799876543\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\n}\n";
   private final FlowResultMapper mapper;
   private final MapperFacade mapperFacade;
   private final Provider<MtanOtpCheck> mtanAuthentication;

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

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "OTP successfully verified.",
               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>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=\"#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>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 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 = "Checks the submitted mTAN OTP. Note that unlike identity verification, approval REST calls require an existing user\nand cannot prevent username enumeration.\n<div class=\"iam-resource-return\">Whether the check was successful. Gives information about the next step required.</div>",
      tags = "SMS/mTAN",
      summary = "Check mTAN OTP"
   )
   @POST
   @Path("/otp/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   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);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Available mTAN tokens retrieved.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PublicSelfServiceMtanTokenDataCollectionDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceMtanTokenDataCollectionDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The number choices are returned as\n<a href=\"#additionalPublicSelfServiceAttributes\">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"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/tokens/retrieve")
   public Response retrieveMtanTokens() {
      FlowResult result = ((MtanOtpCheck)this.mtanAuthentication.get()).getNumberChoice();
      return FlowResultToCollectionDocumentMapper.flowResultDataToCollectionDocumentMapper(this.mapper, this.mapperFacade)
         .map(result, MtanOtpCheckingStep.MtanNumberChoices.class, PublicSelfServiceMtanTokenData.class, AuthenticationFailed.BaseChoiceToken::getId);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "mTAN token successfully selected, OTP sent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:11:42.240+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"public-self-service.approval.mtan.otp.resend.information\",\n\t\t\"id\": \"797271294\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"public-self-service.session\",\n\t  \"id\": \"516534791446228062\",\n\t  \"attributes\": {\n\t\t\"nextAuthStep\": \"MTAN_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41799876543\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\n}\n"
                        )
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:11:42.240+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"public-self-service.approval.mtan.otp.resend.information\",\n\t\t\"id\": \"797271294\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"public-self-service.session\",\n\t  \"id\": \"516534791446228062\",\n\t  \"attributes\": {\n\t\t\"nextAuthStep\": \"MTAN_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41799876543\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\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=\"#nextStepCodes\">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 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 = "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 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=\"#additionalPublicSelfServiceAttributes\">additional attribute</a>\n with the <tt>MTAN_OTP_REQUIRED</tt> next step code.</p></div>",
      tags = "SMS/mTAN",
      summary = "Select token"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/tokens/{id}/select")
   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);
      return selectResult.isFailed() ? this.mapper.toResponse(selectResult) : this.responseWithIncludedResendMtanOptPossibleResource(selectResult);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The OTP has been resent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:11:42.240+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"public-self-service.approval.mtan.otp.resend.information\",\n\t\t\"id\": \"797271294\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"public-self-service.session\",\n\t  \"id\": \"516534791446228062\",\n\t  \"attributes\": {\n\t\t\"nextAuthStep\": \"MTAN_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41799876543\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\n}\n"
                        )
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = PublicSelfServiceFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:11:42.240+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"public-self-service.approval.mtan.otp.resend.information\",\n\t\t\"id\": \"797271294\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"public-self-service.session\",\n\t  \"id\": \"516534791446228062\",\n\t  \"attributes\": {\n\t\t\"nextAuthStep\": \"MTAN_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41799876543\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\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=\"#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 = "Resend 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 = "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=\"#additionalPublicSelfServiceAttributes\">additional attribute</a>\n with the <tt>MTAN_OTP_REQUIRED</tt> next step code.</p></div>",
      tags = "SMS/mTAN",
      summary = "Resend OTP"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/otp/resend")
   public Response resendMtanOtp() {
      FlowResult resendResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtp();
      return resendResult.isFailed() ? this.mapper.toResponse(resendResult) : this.responseWithIncludedResendMtanOptPossibleResource(resendResult);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Returns information about a possible OTP resend.",
      content = {
            @Content(
               mediaType = "application/vnd.api+json",
               schema = @Schema(implementation = PublicSelfServiceResendMtanOtpPossibleDataResourceDocument.class)
            ),
            @Content(mediaType = "application/json", schema = @Schema(implementation = PublicSelfServiceResendMtanOtpPossibleDataResourceDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The resend information is returned as\n<a href=\"#additionalPublicSelfServiceAttributes\">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"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/otp/resend-info/retrieve")
   public Response retrieveResendInfo() {
      FlowResult resendPossibleResult = ((MtanOtpCheck)this.mtanAuthentication.get()).resendOtpPossible();
      return resendPossibleResult.isFailed()
         ? this.mapper.toResponse(resendPossibleResult)
         : Response.ok(this.extractResourceObject(resendPossibleResult).document()).build();
   }

   private Response responseWithIncludedResendMtanOptPossibleResource(FlowResult flowResult) {
      PublicSelfServiceResendMtanOtpPossibleData dataToInclude = new PublicSelfServiceResendMtanOtpPossibleData(
         (Boolean)flowResult.additionalAttributes().get("resendPossible")
      );
      return this.mapper.toResponseWithIncludedResource(flowResult, dataToInclude, PublicSelfServiceResendMtanOtpPossibleData.class);
   }

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