package com.airlock.iam.admin.infrastructure.resource.prot.user;

import ch.ergon.restal.jsonapi.document.data.ResourceDocument;
import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import com.airlock.iam.admin.application.service.user.AdminUserLockService;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.user.UserLockDataResourceDocument;
import com.airlock.iam.admin.infrastructure.service.user.UserIdResolver;
import com.airlock.iam.admin.infrastructure.transfer.request.user.UserLockRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.user.UserLockData;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.user.UserLockState;
import com.airlock.iam.common.infrastructure.resource.Resources;
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.DELETE;
import jakarta.ws.rs.GET;
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 jakarta.ws.rs.core.Response.Status;
import ma.glasnost.orika.MapperFacade;

@Path("/users/{userId}/lock")
@Consumes("application/json")
@Produces({"application/vnd.api+json", "application/json;qs=0.9"})
public class UserLockResource {
   private final Provider<AdminUserLockService> lockServiceProvider;
   private final MapperFacade mapper;
   private final Provider<ExternalUriInfo> uriInfo;
   private final HelpdeskLogger helpdeskLogger;
   private final Provider<UserIdResolver> userIdResolver;

   @Inject
   public UserLockResource(
      Provider<AdminUserLockService> lockServiceProvider,
      MapperFacade mapper,
      Provider<ExternalUriInfo> uriInfo,
      HelpdeskLogger logger,
      Provider<UserIdResolver> userIdResolver
   ) {
      this.lockServiceProvider = lockServiceProvider;
      this.mapper = mapper;
      this.uriInfo = uriInfo;
      this.helpdeskLogger = logger;
      this.userIdResolver = userIdResolver;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "User is locked.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "User is not locked or does not exist.",
               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"))
               }
            )
      }
   )
   @GET
   @Operation(
      description = "Retrieves information about the lock state of the user.\n<div class=\"iam-resource-return\">The available information about the user's lock state.</div>",
      tags = "User Locking",
      summary = "Get lock state"
   )
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieveLockState(@PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      UserLockState lockState = this.lockService().getUserLockState(userId);
      return !lockState.isLocked() ? ErrorResponse.notFound().build().toResponse() : this.buildDocumentResponse(lockState, Status.OK);
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "User was already locked for the same reason.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "201",
               description = "User got locked.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "The request was invalid.",
               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 = "404",
               description = "The user does not exist.",
               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 = "409",
               description = "User was already locked, but for a different reason.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = UserLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = UserLockDataResourceDocument.class))
               }
            )
      }
   )
   @POST
   @Operation(
      description = "Locks the user.\n<div class=\"iam-resource-return\">The lock state which was set.</div>",
      tags = "User Locking",
      summary = "Lock the user"
   )
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response setLockState(
      @PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam,
      @Parameter(description = "The attributes required to lock a user.") @NotNull @Valid UserLockRequest lockRequest
   ) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      UserLockState state = this.lockService().lockUser(userId, lockRequest.getReason());
      if (state.isStateUpdated()) {
         this.helpdeskLogger
            .log(userId, "Administrator ${authentee} locked user ${affectedUser}, with reason %s.", new Object[]{LogUtil.quote(lockRequest.getReason())});
         return this.buildDocumentResponse(state, Status.CREATED);
      } else {
         this.helpdeskLogger
            .log(
               userId,
               "Administrator ${authentee} tried to lock user ${affectedUser} with reason %s but user was already locked, with reason %s.",
               new Object[]{LogUtil.quote(lockRequest.getReason()), LogUtil.quote(state.getReason())}
            );
         Status httpStatus = state.equalReason(lockRequest.getReason()) ? Status.OK : Status.CONFLICT;
         return this.buildDocumentResponse(state, httpStatus);
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "User was unlocked."),
            @ApiResponse(
               responseCode = "404",
               description = "User was not locked before (and is not now) or does not exist.",
               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"))
               }
            )
      }
   )
   @DELETE
   @Operation(
      description = "Unlocks the user.\n<div class=\"iam-resource-return\">Information about the user's lock state.</div>",
      tags = "User Locking",
      summary = "Unlock the user"
   )
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response unlockUser(@PathParam("userId") @Parameter(schema = @Schema(type = "string")) String userIdParam) {
      UserId userId = ((UserIdResolver)this.userIdResolver.get()).resolve(userIdParam);
      UserLockState state = this.lockService().unlockUser(userId);
      if (!state.isStateUpdated()) {
         this.helpdeskLogger.log(userId, "Administrator ${authentee} tried to unlock user ${affectedUser} but user was already unlocked.");
         return ErrorResponse.notFound().build().toResponse();
      } else {
         this.helpdeskLogger.log(userId, "Administrator ${authentee} unlocked user ${affectedUser}.");
         return Response.status(Status.NO_CONTENT).build();
      }
   }

   private Response buildDocumentResponse(UserLockState state, Status httpStatus) {
      ResourceDocument<UserLockData> document = ResourceObject.builder(state.getUserId().getValue(), (UserLockData)this.mapper.map(state, UserLockData.class))
         .linkSelf(Resources.uri((ExternalUriInfo)this.uriInfo.get(), UserLockResource.class).build(new Object[]{state.getUserId()}))
         .document();
      return Response.status(httpStatus).entity(document).build();
   }

   private AdminUserLockService lockService() {
      return (AdminUserLockService)this.lockServiceProvider.get();
   }
}
