package com.airlock.iam.login.rest.infrastructure.resource.prot;

import ch.ergon.restal.jsonapi.document.data.CollectionDocument;
import ch.ergon.restal.jsonapi.document.data.ResourceObject;
import ch.ergon.restal.jsonapi.document.data.ResourceObject.Builder;
import ch.ergon.restal.jsonapi.document.request.RequestDocument;
import com.airlock.iam.base.api.domain.model.exception.EntityNotFoundException;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.common.api.infrastructure.annotation.RequiresWritableSession;
import com.airlock.iam.common.application.exception.RequestDocumentAttributesMissingException;
import com.airlock.iam.common.application.jsonapi.ResourceIdentifierUtils;
import com.airlock.iam.common.domain.model.auth.ExternalizationContext;
import com.airlock.iam.common.domain.model.cronto.ExternalCrontoDeviceId;
import com.airlock.iam.common.domain.service.externalization.ExternalizationService;
import com.airlock.iam.core.domain.model.cronto.CrontoOtp;
import com.airlock.iam.core.domain.model.cronto.challenge.CrontoDeactivationChallenge;
import com.airlock.iam.core.misc.impl.cronto.CrontoActivationSession;
import com.airlock.iam.core.misc.impl.token.cronto.CrontoDeviceToken;
import com.airlock.iam.flow.shared.domain.model.cronto.CompleteDeviceActivationResult;
import com.airlock.iam.flow.shared.domain.model.cronto.CrontoDeviceActivationChallenge;
import com.airlock.iam.flow.shared.domain.model.cronto.StartDeviceActivationResult;
import com.airlock.iam.flow.shared.domain.service.cronto.CrontoSelfManagementService;
import com.airlock.iam.login.rest.application.configuration.CrontoSelfServiceRestConfig;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.my.MyCrontoDeviceActivationChallengeDataResourceDocument;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.my.MyCrontoDeviceActivationCompletedDataResourceDocument;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.my.MyCrontoDeviceDataCollectionDocument;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.my.MyCrontoDeviceDataResourceDocument;
import com.airlock.iam.login.rest.infrastructure.generated.transfer.shared.response.my.MyCrontoDeviceDeletionChallengeDataResourceDocument;
import com.airlock.iam.login.rest.infrastructure.logger.ProtectedHelpdeskLogger;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.request.cronto.CrontoCompleteDeviceActivationRequest;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.request.cronto.CrontoDeviceDataUpdateRequest;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.request.cronto.CrontoStartDeviceActivationRequest;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceActivationChallengeData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceActivationCompletedData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceDeletionChallengeData;
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.PATCH;
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.security.Principal;
import java.util.Optional;
import ma.glasnost.orika.MapperFacade;

@Path("/my/tokens/cronto/devices")
public class UserCrontoSelfServiceResource {
   private final ProtectedHelpdeskLogger helpdeskLogger;
   private final Provider<Principal> principalProvider;
   private final Provider<CrontoSelfManagementService> service;
   private final MapperFacade mapper;
   private final Provider<CrontoSelfServiceRestConfig> selfServiceConfig;
   private final Provider<ExternalizationService> externalizationService;

   @Inject
   public UserCrontoSelfServiceResource(
      ProtectedHelpdeskLogger logger,
      Provider<Principal> principalProvider,
      Provider<CrontoSelfManagementService> service,
      MapperFacade mapper,
      Provider<CrontoSelfServiceRestConfig> selfServiceConfig,
      Provider<ExternalizationService> externalizationService
   ) {
      this.helpdeskLogger = logger;
      this.principalProvider = principalProvider;
      this.service = service;
      this.mapper = mapper;
      this.selfServiceConfig = selfServiceConfig;
      this.externalizationService = externalizationService;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Retrieved available Cronto devices.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = MyCrontoDeviceDataCollectionDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = MyCrontoDeviceDataCollectionDocument.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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 has no Cronto account.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices</tt>.\n<p>Fetching all Cronto devices that the user has.</p>\n<div class=\"iam-resource-return\">All Cronto devices belonging to the user.</div>",
      tags = "Cronto",
      summary = "List devices"
   )
   @GET
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   @RequiresWritableSession
   public Response listCrontoDevices() {
      CollectionDocument<MyCrontoDeviceData> document = new CollectionDocument();
      this.service().getDevicesByUsername(this.getAuthUser()).forEach(device -> document.add(this.toResourceObject(device, MyCrontoDeviceData.class).build()));
      return Response.ok().entity(document).build();
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Retrieved Cronto device.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = MyCrontoDeviceDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = MyCrontoDeviceDataResourceDocument.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"))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The user does not exist or there is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/device/info/retrieve</tt>.\n<p>Retrieves a Cronto device.</p>\n<div class=\"iam-resource-return\">The attributes of the retrieved Cronto device.</div>",
      tags = "Cronto",
      summary = "Get device"
   )
   @GET
   @Path("/{id}")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response retrieveDevice(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      CrontoDeviceToken device = this.service().getDevice(this.getAuthUser(), ExternalCrontoDeviceId.externalDeviceId(id));
      return Response.ok(this.toResourceObject(device, MyCrontoDeviceData.class).document()).build();
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "OTP verified and new challenge created.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = MyCrontoDeviceActivationChallengeDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = MyCrontoDeviceActivationChallengeDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>VALIDATION_FAILED</td>\n<td>The supplied attributes could not be validated successfully. See <a href=\"#validationFailures\">validation failures</a> for detail codes.</td>\n</tr>\n<tr>\n<td>CRONTO_OTP_WRONG</td>\n<td>The provided OTP was wrong.</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",
               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 = "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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/activation/start</tt>.\n<p>Starts adding a new Cronto device.</p>\n<p>Starts adding a new Cronto device by sending the OTP from the Cronto activation letter.\nReturns a session object and a new challenge cryptogram to be responded by a POST to <tt>cronto/devices/activation/complete</tt>.</p>\n<div class=\"iam-resource-return\">A session object and a new challenge cryptogram.</div>",
      tags = "Cronto",
      summary = "Start adding device"
   )
   @POST
   @Path("/activation/start")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response startDeviceActivation(
      @Parameter(description = "Contains the OTP from the Cronto activation letter.") @NotNull @Valid CrontoStartDeviceActivationRequest request
   ) {
      try {
         this.helpdeskLogger.log("Starting to add Cronto device");
         StartDeviceActivationResult result = this.service().startDeviceActivation(this.getAuthUser(), new CrontoOtp(request.getOtp()));
         if (result.isSuccessful()) {
            return Response.status(Status.OK).entity(this.toResourceObject(result.getActivationSession(), result.getActivationChallenge()).document()).build();
         }

         this.helpdeskLogger.log("First challenge OTP for Cronto device activation rejected: %s", result.getFailureReason());
         return ErrorResponse.badRequest().errorCode(result.getFailureReason()::name).build().toResponse();
      } catch (FailureException e) {
         return ErrorResponse.internalServerError().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "OTP verified and new Cronto device stored.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = MyCrontoDeviceActivationCompletedDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = MyCrontoDeviceActivationCompletedDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "400",
               description = "Invalid context or specific error.<div class=\"iam-error-code\"><table>\n<tr>\n<td>VALIDATION_FAILED</td>\n<td>The supplied attributes could not be validated successfully. See <a href=\"#validationFailures\">validation failures</a> for detail codes.</td>\n</tr>\n<tr>\n<td>CRONTO_OTP_WRONG</td>\n<td>The provided OTP was wrong.</td>\n</tr>\n<tr>\n<td>CRONTO_DEVICE_ALREADY_EXISTS</td>\n<td>The same device cannot be added twice.</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"))
               }
            ),
            @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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/activation/complete</tt>.\n<p>Completes adding a new Cronto Device.</p>\n<p>Adds a new Cronto device by verifying the OTP the user has extracted from the cryptogram he has received\nfrom the prior POST to <tt>cronto/devices/activation/start</tt>.</p>\n<div class=\"iam-resource-return\">The attributes of the added Cronto device.</div>",
      tags = "Cronto",
      summary = "Complete adding device"
   )
   @POST
   @Path("/activation/complete")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response completeDeviceActivation(
      @Parameter(description = "Contains the information needed to successfully complete adding the new device.") @NotNull @Valid CrontoCompleteDeviceActivationRequest request
   ) {
      ExternalizationContext externalizationContext = toExternalizationContext(request.getContext());
      CrontoActivationSession session = this.createSessionExternalizer().read(this.getAuthUser(), externalizationContext);
      CompleteDeviceActivationResult result = this.service().completeDeviceActivation(request.getLabel(), new CrontoOtp(request.getOtp()), session);
      if (result.isSuccessful()) {
         CrontoDeviceToken deviceToken = result.getDeviceToken();
         this.helpdeskLogger
            .log("Verified OTP and added new Cronto device with ID '%s' and label '%s'", deviceToken.getDeviceIdForDisplay(), deviceToken.getCustomName());
         return Response.status(Status.OK).entity(this.toResourceObject(deviceToken, MyCrontoDeviceActivationCompletedData.class).document()).build();
      } else {
         this.helpdeskLogger.log("Verification of second Cronto activation OTP failed");
         return ErrorResponse.badRequest().errorCode(result.getFailureReason()::name).build().toResponse();
      }
   }

   private static ExternalizationContext toExternalizationContext(String jwt) {
      return ExternalizationContext.builder().jwt(jwt).build();
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "The device has been deleted and a challenge was retuned.",
               content = {
                     @Content(
                        mediaType = "application/vnd.api+json",
                        schema = @Schema(implementation = MyCrontoDeviceDeletionChallengeDataResourceDocument.class)
                     ),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = MyCrontoDeviceDeletionChallengeDataResourceDocument.class))
               }
            ),
            @ApiResponse(responseCode = "204", description = "The device has been deleted and no challenge was returned."),
            @ApiResponse(
               responseCode = "400",
               description = "The user's only device can't be deleted.",
               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 = "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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices/{id}/remove</tt>.\n<p>Deletes a user's Cronto device.</p>\n<div class=\"iam-resource-return\">an empty response.</div>",
      tags = "Cronto",
      summary = "Delete Cronto device"
   )
   @DELETE
   @Path("/{id}")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response deleteDevice(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);
      String displayId = this.deviceIdForLogging(externalId);

      try {
         Optional<CrontoDeactivationChallenge> challenge = this.service().deleteDevice(this.getAuthUser(), externalId);
         this.helpdeskLogger.log("Deleted Cronto device '%s'", displayId);
         if (challenge.isPresent()) {
            MyCrontoDeviceDeletionChallengeData dto = (MyCrontoDeviceDeletionChallengeData)this.mapper
               .map(challenge.get(), MyCrontoDeviceDeletionChallengeData.class);
            return Response.ok(ResourceObject.builder(ResourceIdentifierUtils.getIdFor(dto), dto).document()).build();
         } else {
            return Response.noContent().build();
         }
      } catch (EntityNotFoundException e) {
         this.helpdeskLogger.log("Request to delete non-existing Cronto device '%s'", externalId);
         return ErrorResponse.notFound().build().toResponse();
      } catch (ViolationException e) {
         this.helpdeskLogger.log("Refused to delete last Cronto device");
         return ErrorResponse.badRequest().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The label has been updated."),
            @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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/device/edit</tt>.\n<p>Updates Cronto device metadata.</p>\n<div class=\"iam-resource-return\">an empty response</div>",
      tags = "Cronto",
      summary = "Update device data"
   )
   @PATCH
   @Path("/{id}")
   @Consumes({"application/vnd.api+json", "application/json"})
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response updateLabel(
      @Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id,
      @Parameter(description = "The new label of the Cronto device.") @NotNull @Valid RequestDocument<CrontoDeviceDataUpdateRequest> requestDocument
   ) {
      CrontoDeviceDataUpdateRequest attributes = RequestDocumentAttributesMissingException.checkAttributesPresent(requestDocument);
      ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);

      try {
         this.service().updateLabel(this.getAuthUser(), externalId, attributes.getLabel());
         this.helpdeskLogger.log("Updated label of Cronto device '%s' to '%s'", this.deviceIdForLogging(externalId), attributes.getLabel());
         return Response.noContent().build();
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      } catch (ViolationException e) {
         this.helpdeskLogger.log("Cannot update label with blank value");
         return ErrorResponse.badRequest().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The device has been enabled."),
            @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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices/{id}/enable</tt>.\n<p>Enables a Cronto device.</p>\n<div class=\"iam-resource-return\">an empty response</div>",
      tags = "Cronto",
      summary = "Enable Cronto device"
   )
   @POST
   @Path("/{id}/enable")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response enableDevice(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      try {
         ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);
         this.service().enableDevice(this.getAuthUser(), externalId);
         this.helpdeskLogger.log("Enabled Cronto device '%s'", this.deviceIdForLogging(externalId));
         return Response.noContent().build();
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The device has been disabled."),
            @ApiResponse(
               responseCode = "400",
               description = "The user's last device can't be disabled.",
               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 = "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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices/{id}/disable</tt>.\n<p>Disables a Cronto device.</p>\n<div class=\"iam-resource-return\">an empty response</div>",
      tags = "Cronto",
      summary = "Disable Cronto device"
   )
   @DELETE
   @Path("/{id}/enable")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response disableDevice(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      try {
         ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);
         this.service().disableDevice(this.getAuthUser(), externalId);
         this.helpdeskLogger.log("Disabled Cronto device '%s'", this.deviceIdForLogging(externalId));
         return Response.noContent().build();
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      } catch (ViolationException e) {
         this.helpdeskLogger.log("Cannot disable last Cronto device of user");
         return ErrorResponse.badRequest().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "Push notifications have been allowed."),
            @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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices/{id}/push/enable</tt>.\n<p>Allows use of the push notification feature with a Cronto device. To actually send push notifications to this device,\nit must first register a notification identifier. This typically happens upon the next login.</p>\n<div class=\"iam-resource-return\">an empty response</div>",
      tags = "Cronto",
      summary = "Allow push"
   )
   @POST
   @Path("/{id}/push/allow")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response allowPushNotifications(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      try {
         ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);
         this.service().allowPushNotifications(this.getAuthUser(), externalId);
         this.helpdeskLogger.log("Allowed push notifications for Cronto device '%s'", this.deviceIdForLogging(externalId));
         return Response.noContent().build();
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      }
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "Push notifications have been disallowed."),
            @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"))
               }
            ),
            @ApiResponse(
               responseCode = "403",
               description = "Access to the requested service is forbidden. Authentication will not help.",
               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 = "There is no device with this ID.",
               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 = "> [!caution]\n> This endpoint is deprecated and replaced by the protected self-service flow functionality. See\n<tt>/protected/self-service/cronto/devices/{id}/push/disable</tt>.\n<p>Disallows push notification for a Cronto device.</p>\n<div class=\"iam-resource-return\">an empty response</div>",
      tags = "Cronto",
      summary = "Disallow push"
   )
   @DELETE
   @Path("/{id}/push/allow")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Deprecated
   public Response disallowPushNotifications(@Parameter(description = "The ID of the Cronto device.") @PathParam("id") String id) {
      try {
         ExternalCrontoDeviceId externalId = ExternalCrontoDeviceId.externalDeviceId(id);
         this.service().disallowPushNotification(this.getAuthUser(), externalId);
         this.helpdeskLogger.log("Disallowed push notifications for Cronto device '%s'", this.deviceIdForLogging(externalId));
         return Response.noContent().build();
      } catch (EntityNotFoundException e) {
         return ErrorResponse.notFound().build().toResponse();
      }
   }

   private CrontoSelfManagementService service() {
      return (CrontoSelfManagementService)this.service.get();
   }

   private UserId getAuthUser() {
      return UserId.userId(((Principal)this.principalProvider.get()).getName());
   }

   private <T> Builder<T> toResourceObject(CrontoDeviceToken device, Class<T> targetClass) {
      return ResourceObject.builder(device.getSerialId(), this.mapper.map(device, targetClass));
   }

   private Builder<MyCrontoDeviceActivationChallengeData> toResourceObject(CrontoActivationSession session, CrontoDeviceActivationChallenge challenge) {
      Optional<ExternalizationContext> context = this.createSessionExternalizer().write(session);
      if (context.isEmpty()) {
         throw new FailureException("couldn't externalize session context");
      }

      MyCrontoDeviceActivationChallengeData.MyCrontoDeviceActivationChallengeDataBuilder builder = MyCrontoDeviceActivationChallengeData.builder()
         .challengeImage(challenge.getChallengeImage().base64image())
         .context(context.get().getJwt());
      challenge.getSecureChannelChallenge().ifPresent(builder::secureChannelChallenge);
      MyCrontoDeviceActivationChallengeData dto = builder.build();
      return ResourceObject.builder(ResourceIdentifierUtils.getIdFor(dto), dto);
   }

   private CrontoActivationSessionExternalizer createSessionExternalizer() {
      return CrontoActivationSessionExternalizer.builder()
         .selfServiceConfig((CrontoSelfServiceRestConfig)this.selfServiceConfig.get())
         .externalizationService((ExternalizationService)this.externalizationService.get())
         .build();
   }

   private String deviceIdForLogging(ExternalCrontoDeviceId externalId) {
      return this.service().getDeviceIdForLogging(this.getAuthUser(), externalId);
   }
}
