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

import com.airlock.iam.authentication.application.service.devicetoken.DeviceTokenAuthentication;
import com.airlock.iam.authentication.infrastructure.generated.transfer.response.DeviceTokenChallengeDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.transfer.request.DeviceTokenResponseCheckRequest;
import com.airlock.iam.authentication.infrastructure.transfer.response.DeviceTokenChallengeData;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.infrastructure.mapper.FlowResultMapper;
import com.airlock.iam.flow.auth.api.infrastructure.generated.transfer.response.AuthenticationFlowResultDataResourceDocument;
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.validation.constraints.Size;
import jakarta.ws.rs.Consumes;
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("/device-token")
public class DeviceTokenFlowResource {
   private final FlowResultMapper mapper;
   private final Provider<DeviceTokenAuthentication> deviceTokenAuthentication;

   @Inject
   public DeviceTokenFlowResource(FlowResultMapper mapper, Provider<DeviceTokenAuthentication> deviceTokenAuthentication) {
      this.mapper = mapper;
      this.deviceTokenAuthentication = deviceTokenAuthentication;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "A challenge has been returned.",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = DeviceTokenChallengeDataResourceDocument.class)),
                     @Content(mediaType = "application/json", schema = @Schema(implementation = DeviceTokenChallengeDataResourceDocument.class))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "No device token with the specified ID found (only if the user is authenticated or fail on challenge retrieval is enabled).",
               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 = "Get device token challenge.\n<p>\nReturns a challenge for the device token authentication step.\n</p>\n<p>\nThe challenge is a random alphanumeric string. It has to be included in a JWT and signed by the device token key.\n</p>\n<p>\nNote that, by default a challenge is <b>always</b> issued (status code 200) for users who are not authenticated, regardless of whether the device ID is known. This is done to prevent\nenumeration attacks. The retrieval for unauthenticated users can be configured to fail (status code 404) if the device ID is unknown.\n</p>\n<div class=\"iam-resource-return\">A challenge string.</div>",
      tags = "Device Tokens",
      summary = "Get challenge"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Path("/{deviceTokenId}/challenge/retrieve")
   public Response retrieveChallenge(
      @Parameter(description = "The ID of the client device token.") @PathParam("deviceTokenId") @Size(min = 1, max = 50) String deviceTokenId
   ) {
      FlowResult result = ((DeviceTokenAuthentication)this.deviceTokenAuthentication.get()).getChallenge(deviceTokenId);
      if (result.isFailed()) {
         Optional<StepError> error = result.error();
         return error.isPresent() && error.get() == GenericStepError.NO_VALID_TOKEN
            ? Response.status(Status.NOT_FOUND).build()
            : this.mapper.toResponse(result);
      } else {
         return this.mapper.toResponseWithData(result, DeviceTokenChallengeData.class);
      }
   }

   @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 = "403",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>DEVICE_TOKEN_VERIFICATION_FAILED</td>\n<td>\nDevice token verification failed for one of the following reasons:\n\t\t\t<ul>\n \t\t\t\t<li>Signature on JWT is wrong</li>\n \t\t\t\t<li>Signed wrong challenge</li>\n \t\t\t\t<li>Challenge expired</li>\n \t\t\t\t<li>Device token expired</li>\n \t\t\t\t<li>Device token disabled</li>\n\t\t\t</ul>\n\t\t</td>\n\t</tr>\n</table>\n</div>\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 = "Verify device token JWT.\n<p>\nVerifies the submitted JWT. The JWT must contain at least the following claims:\n<ul>\n<li>\"challenge\": The challenge.</li>\n</ul>\nIt must be signed by the device token key using the same algorithm as in the device token creation process.\n</p>\n<div class=\"iam-resource-return\">Whether the check was successful.</div>",
      tags = "Device Tokens",
      summary = "Verify JWT"
   )
   @POST
   @Path("/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response checkJwt(@Parameter(description = "The JWT to be verified.") @NotNull @Valid DeviceTokenResponseCheckRequest request) {
      FlowResult result = ((DeviceTokenAuthentication)this.deviceTokenAuthentication.get()).checkJwt(request.getJwt());
      return this.mapper.toResponse(result);
   }
}
