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

import ch.ergon.restal.jsonapi.document.error.ErrorData;
import com.airlock.iam.authentication.infrastructure.transfer.request.AuthenticationUserIdentificationRequest;
import com.airlock.iam.authentication.infrastructure.transfer.request.UsernameCheckRequest;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.infrastructure.mapper.ValidationToErrorMapper;
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.domain.model.ValidationStepResultData;
import com.airlock.iam.flow.shared.application.service.identification.UserIdentification;
import com.airlock.iam.flow.shared.infrastructure.mapper.UserIdentificationFieldMapper;
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;
import java.util.List;
import java.util.Map;

@Path("/")
public class UserIdentificationFlowResource {
   private static final UserIdentificationFieldMapper IDENTIFICATION_MAPPER = new UserIdentificationFieldMapper();
   private final Provider<UserIdentification> userIdentification;
   private final Provider<FlowResultMapper> flowResultMapper;
   private final Provider<ValidationToErrorMapper> errorMapper;

   @Inject
   public UserIdentificationFlowResource(
      Provider<UserIdentification> identification, Provider<FlowResultMapper> flowResultMapper, Provider<ValidationToErrorMapper> errorMapper
   ) {
      this.userIdentification = identification;
      this.flowResultMapper = flowResultMapper;
      this.errorMapper = errorMapper;
   }

   @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>USER_NOT_FOUND</td>\n<td>The user does not exist.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>USERNAME_REQUIRED</tt>.\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"))
               }
            )
      }
   )
   @Deprecated
   @Operation(
      description = "> [!caution]\n> This endpoint is deprecated. Use <tt>/public/authentication/user/identify/</tt> instead.\n\n> [!important]\n> <strong>CAPTCHA</strong>: When protection is enabled, a challenge is included in the previous response.\n<p>Identifies the user.</p>\n<div class=\"iam-resource-return\">Whether the user has been successfully identified. If the attribute <tt>nextStep</tt> is present in the response,\n further authentication steps are required. If the attribute is missing, authentication was successful.</div>",
      tags = "User",
      summary = "Identify by username"
   )
   @POST
   @Path("/username/identify")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkUsernameDeprecated(@Parameter(description = "Contains the username.") @NotNull @Valid UsernameCheckRequest request) {
      return this.identifyUser(request.getUsername(), null);
   }

   @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>USER_NOT_FOUND</td>\n<td>The user does not exist.</td>\n</tr>\n</table>\n</div>\n<br>Possible next step: <tt>USERNAME_REQUIRED</tt>.\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 = "> [!important]\n> <strong>CAPTCHA</strong>: When protection is enabled, a challenge is included in the previous response.\n<p>Identifies the user based on their username or the provided user data items.</p>\n<div class=\"iam-resource-return\">Whether the user has been successfully identified. If the attribute <tt>nextStep</tt> is present in the response,\n further authentication steps are required. If the attribute is missing, authentication was successful.</div>",
      tags = "User",
      summary = "Identify user"
   )
   @POST
   @Path("/user/identify")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response identifyUser(@Parameter(description = "Contains the username.") @NotNull @Valid AuthenticationUserIdentificationRequest request) {
      return this.identifyUser(request.getUsername(), request.getData());
   }

   private Response identifyUser(String username, Map<String, String> userData) {
      FlowResult flowResult = ((UserIdentification)this.userIdentification.get()).identifyUser(username, userData);
      return this.mapToResponse(flowResult);
   }

   private Response mapToResponse(FlowResult flowResult) {
      ValidationResult validationResult = flowResult.data(ValidationStepResultData.class)
         .map(ValidationStepResultData::result)
         .orElse(ValidationResult.valid());
      if (validationResult.isNotValid()) {
         List<ErrorData> errorData = ((ValidationToErrorMapper)this.errorMapper.get())
            .withFieldToSourcePointerMapper(IDENTIFICATION_MAPPER)
            .toErrorData(validationResult.getErrors());
         return ((FlowResultMapper)this.flowResultMapper.get()).toResponse(flowResult, errorData);
      } else {
         return ((FlowResultMapper)this.flowResultMapper.get()).toResponse(flowResult);
      }
   }
}
