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

import ch.ergon.restal.jsonapi.document.data.ResourceDocument;
import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.infrastructure.generated.transfer.response.techclient.PlainTechClientLockDataResourceDocument;
import com.airlock.iam.admin.infrastructure.transfer.request.techclient.PlainTechClientLockRequest;
import com.airlock.iam.admin.infrastructure.transfer.response.techclient.PlainTechClientLockData;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.base.infrastructure.mapper.entity.RestMapper;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.locking.LockInfo;
import com.airlock.iam.common.infrastructure.exception.IamRestErrorCode;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.techclient.admin.application.service.PlainTechClientService;
import com.airlock.iam.techclient.admin.domain.model.PlainTechClient;
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 java.util.Optional;

@Path("/tech-clients/{id}/lock")
public class TechClientLockResource {
   private final RestMapper mapper;
   private final Provider<PlainTechClientService> plainTechClientService;
   private final Provider<CurrentAdministrator> currentAdministrator;
   private final SigningLogger signingLogger;

   @Inject
   public TechClientLockResource(
      RestMapper mapper,
      Provider<PlainTechClientService> plainTechClientService,
      Provider<CurrentAdministrator> currentAdministrator,
      SigningLogger signingLogger
   ) {
      this.mapper = mapper;
      this.plainTechClientService = plainTechClientService;
      this.currentAdministrator = currentAdministrator;
      this.signingLogger = signingLogger;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The technical client is locked.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The technical client 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 specified technical client.\n<div class=\"iam-resource-return\">The available information about the technical client's lock state.</div>",
      tags = "Technical Clients",
      summary = "Get lock state"
   )
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieveClientLockInfo(@Parameter(description = "The identification of the technical client.") @PathParam("id") String id) {
      Optional<PlainTechClient> client = this.plainTechClientService().findTechClient(TechClientResource.techClientIdentity(id));
      if (client.isEmpty()) {
         return techClientNotFound();
      }

      if (client.get().getLockInfo().isEmpty()) {
         return ErrorResponse.notFound().build().toResponse();
      }

      ResourceDocument<PlainTechClientLockData> document = this.toLockInfoDocument(client.get());
      return Response.ok(document).build();
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The technical client was already locked. The lock reason may have been updated.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "201",
               description = "The technical client has been locked.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = PlainTechClientLockDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The technical client 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 = "The technical client was already locked, but for a different reason.",
               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"))
               }
            )
      }
   )
   @POST
   @Operation(
      description = "Locks the specified technical client.\n<div class=\"iam-resource-return\">The lock state which was set.</div>",
      tags = "Technical Clients",
      summary = "Lock technical client"
   )
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response lockClient(
      @Parameter(description = "The identification of the technical client.") @PathParam("id") String id,
      @Parameter(description = "The attributes required to lock a technical client.") @NotNull @Valid PlainTechClientLockRequest request
   ) {
      Optional<PlainTechClient> client = this.plainTechClientService().findTechClient(TechClientResource.techClientIdentity(id));
      if (client.isEmpty()) {
         return techClientNotFound();
      } else {
         PlainTechClient techClient = client.get();
         String lockReason = request.getReason();
         if (techClient.isLockedWithReason(lockReason)) {
            this.signingLogger
               .log(
                  "Administrator %s tried to lock the technical client with ID %s but the client is already locked for the same reason (%s)",
                  new Object[]{LogUtil.quote(this.currentAdmin()), LogUtil.quote(id), lockReason}
               );
            return this.toDocumentResponse(Status.OK, techClient);
         } else if (techClient.isLocked()) {
            this.signingLogger
               .log(
                  "Administrator %s tried to lock the technical client with ID %s and reason %s but the client is already locked with a different reason (%s)",
                  new Object[]{LogUtil.quote(this.currentAdmin()), LogUtil.quote(id), LogUtil.quote(lockReason), techClient.getLockInfo()}
               );
            return ErrorResponse.conflict().build().toResponse();
         } else {
            techClient.lock(lockReason);
            PlainTechClient updatedClient = this.plainTechClientService().update(techClient);
            this.signingLogger
               .log(
                  "Administrator %s locked the technical client with ID %s because of %s",
                  new Object[]{LogUtil.quote(this.currentAdmin()), LogUtil.quote(id), LogUtil.quote(lockReason)}
               );
            return this.toDocumentResponse(Status.CREATED, updatedClient);
         }
      }
   }

   private Response toDocumentResponse(Status status, PlainTechClient client) {
      ResourceDocument<PlainTechClientLockData> document = this.toLockInfoDocument(client);
      return Response.status(status).entity(document).build();
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The technical client has been unlocked."),
            @ApiResponse(
               responseCode = "404",
               description = "The technical client does not exist or is not locked.",
               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 specified technical client.\n<div class=\"iam-resource-return\">response</div>",
      tags = "Technical Clients",
      summary = "Unlock technical client"
   )
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response unlockClient(@Parameter(description = "The identification of the technical client.") @PathParam("id") String id) {
      Optional<PlainTechClient> client = this.plainTechClientService().findTechClient(TechClientResource.techClientIdentity(id));
      if (client.isEmpty()) {
         return techClientNotFound();
      } else {
         PlainTechClient techClient = client.get();
         if (!techClient.isLocked()) {
            this.signingLogger
               .log(
                  "Administrator %s tried to unlock the technical client with ID %s but the client was not locked",
                  new Object[]{LogUtil.quote(this.currentAdmin()), LogUtil.quote(id)}
               );
            return ErrorResponse.notFound().build().toResponse();
         } else {
            techClient.unlock();
            this.plainTechClientService().update(techClient);
            this.signingLogger
               .log("Administrator %s unlocked the technical client with ID %s", new Object[]{LogUtil.quote(this.currentAdmin()), LogUtil.quote(id)});
            return Response.noContent().build();
         }
      }
   }

   private static Response techClientNotFound() {
      return ErrorResponse.notFound().errorCode(IamRestErrorCode.TECH_CLIENT_NOT_FOUND).build().toResponse();
   }

   private String currentAdmin() {
      return ((CurrentAdministrator)this.currentAdministrator.get()).getName();
   }

   private ResourceDocument<PlainTechClientLockData> toLockInfoDocument(PlainTechClient client) {
      LockInfo lockInfo = (LockInfo)client.getLockInfo().orElseThrow(() -> new FailureException("cannot create client lock response"));
      return this.mapper.forElement(lockInfo, PlainTechClientLockData.class).withIdSupplier(id -> client.getId().asString()).toResourceDocument();
   }

   private PlainTechClientService plainTechClientService() {
      return (PlainTechClientService)this.plainTechClientService.get();
   }
}
