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

import com.airlock.iam.authentication.infrastructure.generated.transfer.response.CrontoActivationChallengeDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.transfer.request.CrontoCompleteDeviceActivationFlowRequest;
import com.airlock.iam.authentication.infrastructure.transfer.request.CrontoStartDeviceActivationFlowRequest;
import com.airlock.iam.authentication.infrastructure.transfer.response.CrontoActivationChallengeData;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.common.domain.model.user.UserIdentityLogs;
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.shared.domain.model.cronto.CrontoActivation;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
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.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;

@Path("/cronto/activation")
public class CrontoActivationAuthenticationFlowResource {
   private final Provider<CrontoActivation> crontoActivation;
   private final Provider<HelpdeskLogger> helpdeskLogger;
   private final Provider<PrincipalInfo> principalInfo;
   private final FlowResultMapper mapper;

   @Inject
   public CrontoActivationAuthenticationFlowResource(
      Provider<CrontoActivation> crontoActivation, Provider<HelpdeskLogger> helpdeskLogger, Provider<PrincipalInfo> principalInfo, FlowResultMapper mapper
   ) {
      this.crontoActivation = crontoActivation;
      this.helpdeskLogger = helpdeskLogger;
      this.principalInfo = principalInfo;
      this.mapper = mapper;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Cronto activation challenge created.\n<br>Next step: <tt>CRONTO_ACTIVATION_START_WITHOUT_LETTER_REQUIRED</tt> (as documented <a href=\"#nextAuthStepCodes\">here</a>).\n",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = CrontoActivationChallengeDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = CrontoActivationChallengeDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>UNEXPECTED_CALL</td>\n<td>This is not a Cronto activation without letter use case.</td>\n</tr>\n</table>\n</div>\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"))
               }
            )
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The challenge information is returned as <a href=\"#additionalAuthAttributes\">additional attribute</a> with the\n<tt>CRONTO_ACTIVATION_START_WITHOUT_LETTER_REQUIRED</tt> next step code.\n<p>Retrieves the start Cronto activation cryptogram for an activation without letter.</p>\n<p>The retrieved Cronto activation challenge can be used to start the Cronto activation.\n<br>The following preconditions must be fulfilled to make this step available:\n<br>Config-related settings:</p>\n<ul>\n<li>The Cronto Activation step must have a \"Strong Authentication Tag\" configured (Advanced Settings).</li>\n<li>The Cronto Handler plugin must have \"Allow Activation Without Letter\" enabled (Activation Settings).</li>\n</ul>\nUser-related conditions:\n<ul>\n<li>The user must not have an activation letter (can be checked on the Adminapp user details page).</li>\n</ul>\n<div class=\"iam-resource-return\">The Cronto activation challenge to start the Cronto device activation process.</div>",
      tags = "Cronto",
      summary = "Get cryptogram"
   )
   @POST
   @Path("/start-challenge/retrieve")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieveFirstActivationChallenge() {
      FlowResult flowResult = ((CrontoActivation)this.crontoActivation.get()).retrieveFirstActivationChallenge();
      return this.mapper.toResponseWithData(flowResult, CrontoActivationChallengeData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "OTP verified and new challenge created.\n<br>Next step: <tt>CRONTO_ACTIVATION_COMPLETE_REQUIRED</tt> (as documented <a href=\"#nextAuthStepCodes\">here</a>).\n",
               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_ACTIVATION_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 = "<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<tr>\n<td>CRONTO_LETTER_INVALID</td>\n<td>The activation letter is not valid.</td>\n</tr>\n<tr>\n<td>ILLEGAL_CRONTO_PLATFORM</td>\n<td>The platform of the activation letter is not allowed.</td>\n</tr>\n<tr>\n<td>CRONTO_DEVICE_LIMIT_REACHED</td>\n<td>The maximum number of devices has been reached.</td>\n</tr>\n<tr>\n<td>CRONTO_LETTER_USAGE_LIMIT_REACHED</td>\n<td>The maximum number of activation letter uses has been reached.</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 first Cronto device activation OTP.\nIf the user has a Cronto activation letter, the request contains the OTP from scanning the\ncryptogram on the letter.\nIf the user is allowed to activate a Cronto device without a letter, the request contains the OTP\nfrom the first activation challenge, which was returned as <a href=\"#additionalAuthAttributes\">additional attribute</a>\nwith the <tt>CRONTO_ACTIVATION_START_WITHOUT_LETTER_REQUIRED</tt> next step code.\n<div class=\"iam-resource-return\">Whether the check was successful. In successful responses, the attribute <tt>nextAuthStep</tt> will be present\n which indicates that further steps are required to successfully activate the Cronto device.</div>",
      tags = "Cronto",
      summary = "Verify OTP"
   )
   @POST
   @Path("/start")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response verifyFirstOtp(
      @Parameter(description = "Contains the OTP from the first Cronto activation challenge.") @NotNull @Valid CrontoStartDeviceActivationFlowRequest request
   ) {
      FlowResult flowResult = ((CrontoActivation)this.crontoActivation.get()).checkFirstActivationOtp(new CrontoOtp(request.getFirstOtp()));
      return this.mapper.toResponse(flowResult);
   }

   @ApiResponse(
      responseCode = "200",
      description = "Retrieved new Cronto challenge.",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = CrontoActivationChallengeDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = CrontoActivationChallengeDataResourceDocument.class))
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. The challenge information is returned as <a href=\"#additionalAuthAttributes\">additional attribute</a> with the\n<tt>CRONTO_ACTIVATION_COMPLETE_REQUIRED</tt> next step code.\n<p>Provides the second Cronto activation cryptogram, which is used to complete the device activation\nprocess. This can be retrieved once the OTP from the first cryptogram has successfully been\nverified.</p>\n<div class=\"iam-resource-return\">The second Cronto cryptogram required to complete the Cronto device activation process.</div>",
      tags = "Cronto",
      summary = "Get second cryptogram"
   )
   @POST
   @Path("/challenge/retrieve")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieveSecondActivationChallenge() {
      FlowResult flowResult = ((CrontoActivation)this.crontoActivation.get()).retrieveSecondActivationChallenge();
      return this.mapper.toResponseWithData(flowResult, CrontoActivationChallengeData.class);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Cronto device activation completed. 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_ACTIVATION_COMPLETE_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<tr>\n<td>CRONTO_DEVICE_ALREADY_EXISTS</td>\n<td>Cronto device already activated.</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 = "Completes activating a new Cronto Device.\nActivates a new Cronto device by verifying the OTP the user has extracted from the cryptogram that was returned\nas <a href=\"#additionalAuthAttributes\">additional attribute</a> with the <tt>CRONTO_ACTIVATION_COMPLETE_REQUIRED</tt> next step code.\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 = "Complete activation"
   )
   @POST
   @Path("/complete")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response verifySecondOtp(
      @Parameter(description = "Contains the second OTP and the device label for the newly activated Cronto device.") @NotNull @Valid CrontoCompleteDeviceActivationFlowRequest request
   ) {
      FlowResult flowResult = ((CrontoActivation)this.crontoActivation.get())
         .checkSecondActivationOtp(new CrontoOtp(request.getSecondOtp()), request.getLabel());
      if (!flowResult.isFailed()) {
         ((HelpdeskLogger)this.helpdeskLogger.get())
            .log(
               UserIdentityLogs.orElseNotAvailable(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity()),
               "User ${affectedUser} has successfully activated a new Cronto device."
            );
      }

      return this.mapper.toResponse(flowResult);
   }
}
