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

import com.airlock.iam.authentication.infrastructure.generated.transfer.response.AuthenticationMtanRegistrationTokenInformationDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.generated.transfer.response.ResendMtanOtpPossibleDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.transfer.request.AuthenticationCheckIakRequest;
import com.airlock.iam.authentication.infrastructure.transfer.request.MtanTokenRegistrationAuthRequest;
import com.airlock.iam.authentication.infrastructure.transfer.response.AuthenticationMtanRegistrationTokenInformationData;
import com.airlock.iam.authentication.infrastructure.transfer.response.ResendMtanOtpPossibleData;
import com.airlock.iam.common.api.domain.model.otp.UnverifiedOtp;
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.ValidationResultMapper;
import com.airlock.iam.flow.shared.application.service.MtanVerification;
import com.airlock.iam.flow.shared.application.service.data.UserDataEdit;
import com.airlock.iam.flow.shared.domain.model.mtan.Iak;
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.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.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;

@Path("/mtan/registration")
public class MtanRegistrationAuthResource {
   private static final String FLOW_RESULT_EXAMPLE_RESPONSE = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:14:11.518+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"authentication.mtan.otp.resend.information\",\n\t\t\"id\": \"1434690641\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"authentication.session\",\n\t  \"id\": \"263074448545407763\",\n\t  \"attributes\": {\n\t\t\"nextStep\": \"MTAN_REGISTRATION_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41761234567\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\n}\n";
   private final FlowResultMapper mapper;
   private final Provider<FlowResultMapper> flowResultMapper;
   private final Provider<MtanVerification> verfication;
   private final Provider<UserDataEdit> edit;
   private final Provider<ValidationResultMapper> stepErrorResultMapper;

   @Inject
   public MtanRegistrationAuthResource(
      FlowResultMapper mapper,
      Provider<FlowResultMapper> flowResultMapper,
      Provider<MtanVerification> verfication,
      Provider<UserDataEdit> edit,
      Provider<ValidationResultMapper> stepErrorResultMapper
   ) {
      this.mapper = mapper;
      this.flowResultMapper = flowResultMapper;
      this.verfication = verfication;
      this.edit = edit;
      this.stepErrorResultMapper = stepErrorResultMapper;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Token fields successfully set. 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>VALIDATION_FAILED</td>\n<td>The supplied fields could not be validated successfully. See <a href=\"#validationFailures\">validation failures</a> for detail codes.</td>\n</tr>\n</table>\n</div>\n<br>Possible next steps: <tt>MTAN_REGISTRATION_START_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 = "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 = "Starts mTAN token registration. Allows setting the number and/or label of an mTAN token.\n<div class=\"iam-resource-return\">Whether the fields have been accepted and set. This endpoint returns a <tt>nextAuthStep</tt> with\n value <tt>MTAN_REGISTRATION_START_REQUIRED</tt> if the token data is not yet valid.\n <p>\n On error, this endpoint returns a detailed list of validation failures.\n </p></div>",
      tags = "SMS/mTAN",
      summary = "Start token registration"
   )
   @Path("start")
   @POST
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response start(@Parameter(description = "the mTAN token to register") @NotNull @Valid MtanTokenRegistrationAuthRequest request) {
      Map<String, Object> data = new HashMap<>();
      data.put("number", request.getNumber());
      if (request.getLabel() != null) {
         data.put("label", request.getLabel());
      }

      FlowResult editResult = ((UserDataEdit)this.edit.get()).editData(data);
      return editResult.isFailed()
         ? ((ValidationResultMapper)this.stepErrorResultMapper.get()).toValidationResponse(editResult)
         : ((ValidationResultMapper)this.stepErrorResultMapper.get()).toValidationResponse(((UserDataEdit)this.edit.get()).validateAndContinue());
   }

   @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>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_REGISTRATION_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>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 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 mTAN OTP.\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 = "SMS/mTAN",
      summary = "Verify mTAN OTP"
   )
   @POST
   @Path("/otp/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkVerificationOtp(@Parameter(description = "Request containing the mTAN OTP.") @NotNull @Valid GenericOtpCheckRequest request) {
      FlowResult result = ((MtanVerification)this.verfication.get()).verifyOtp(UnverifiedOtp.unverifiedOtp(request.getOtp()));
      return this.mapper.toResponse(result);
   }

   @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>IAK_WRONG</td>\n<td>IAK could not be validated successfully. Retry with correct IAK.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>MTAN_REGISTRATION_IAK_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>IAK_WRONG</td>\n<td>IAK 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 IAK.\nThe IAK is typically sent to the user by letter and can only be used once.\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 = "SMS/mTAN",
      summary = "Verify IAK"
   )
   @POST
   @Path("/iak/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkIak(@Parameter(description = "Request containing the IAK.") @NotNull @Valid AuthenticationCheckIakRequest request) {
      FlowResult result = ((MtanVerification)this.verfication.get()).verifyIak(new Iak(request.getIak()));
      return this.mapper.toResponse(result);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Returns information about a possible OTP resend.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = ResendMtanOtpPossibleDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = ResendMtanOtpPossibleDataResourceDocument.class))
      }
   )
   @Operation(
      description = "Returns whether a new OTP can be resent by SMS.\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 = ((MtanVerification)this.verfication.get()).isResendOtpPossible();
      return this.mapper.toResponseWithData(resendPossibleResult, ResendMtanOtpPossibleData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The OTP has been resent.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:14:11.518+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"authentication.mtan.otp.resend.information\",\n\t\t\"id\": \"1434690641\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"authentication.session\",\n\t  \"id\": \"263074448545407763\",\n\t  \"attributes\": {\n\t\t\"nextStep\": \"MTAN_REGISTRATION_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41761234567\",\n\t\t\"resendPossible\": false\n\t  }\n\t}\n}\n"
                        )
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = AuthenticationFlowResultDataResourceDocument.class),
                        examples = @ExampleObject(
                           name = "Flow result",
                           value = "{\n\t\"meta\": {\n\t  \"type\": \"jsonapi.metadata.document\",\n\t  \"timestamp\": \"2025-07-03T11:14:11.518+02:00\"\n\t},\n\t\"included\": [\n\t  {\n\t\t\"type\": \"authentication.mtan.otp.resend.information\",\n\t\t\"id\": \"1434690641\",\n\t\t\"attributes\": {\n\t\t  \"otpResendPossible\": false\n\t\t}\n\t  }\n\t],\n\t\"data\": {\n\t  \"type\": \"authentication.session\",\n\t  \"id\": \"263074448545407763\",\n\t  \"attributes\": {\n\t\t\"nextStep\": \"MTAN_REGISTRATION_OTP_REQUIRED\",\n\t\t\"phoneNumber\": \"+41761234567\",\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_REGISTRATION_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 = "Resend 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 = "Resends the mTAN OTP by SMS.\n<div class=\"iam-resource-return\">Whether the resend was successful and informs about the <tt>nextAuthStep</tt>.\n Additionally, the response includes information about the possibility of an OTP resend.</div>",
      tags = "SMS/mTAN",
      summary = "Resend registration OTP"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/otp/resend")
   public Response resendMtanOtp() {
      FlowResult resendResult = ((MtanVerification)this.verfication.get()).resendOtp();
      ResendMtanOtpPossibleData dataToInclude = new ResendMtanOtpPossibleData((Boolean)resendResult.additionalAttributes().get("resendPossible"));
      return this.mapper.toResponseWithIncludedResource(resendResult, dataToInclude, ResendMtanOtpPossibleData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The information has successfully been returned.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = AuthenticationMtanRegistrationTokenInformationDataResourceDocument.class)
                     ),
                     @Content(
                        mediaType = "application/json",
                        schema = @Schema(implementation = AuthenticationMtanRegistrationTokenInformationDataResourceDocument.class)
                     )
               }
            ),
            @ApiResponse(
               responseCode = "401",
               description = "Invalid or missing authentication.",
               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 = "Retrieves information about the mTAN token to be registered.\nThe information contains the current values and if the label is required.\n<div class=\"iam-resource-return\">Number and label of the mTAN token to be registered.</div>",
      tags = "SMS/mTAN",
      summary = "Get token info"
   )
   @Path("info/retrieve")
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieveInfo() {
      FlowResult result = ((UserDataEdit)this.edit.get()).retrieveEditInfo();
      return ((FlowResultMapper)this.flowResultMapper.get()).toResponseWithData(result, AuthenticationMtanRegistrationTokenInformationData.class);
   }
}
