package com.airlock.iam.oauth2.infrastructure.resource.rest;

import com.airlock.iam.common.api.infrastructure.annotation.DisableCsrfFilter;
import com.airlock.iam.common.infrastructure.request.FormParamHttpServletRequestWrapper;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.application.service.par.OAuth2ParEndpointService;
import com.airlock.iam.oauth2.domain.model.par.OAuth2ParError;
import com.airlock.iam.oauth2.domain.model.par.OAuth2ParResponse;
import com.airlock.iam.oauth2.infrastructure.transfer.response.OAuth2ErrorData;
import com.airlock.iam.oauth2.infrastructure.transfer.response.OAuth2ParData;
import com.airlock.iam.oauth2.infrastructure.validation.AuthorizationServerIdValidator;
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.media.Schema.AdditionalPropertiesValue;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
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.servlet.http.HttpServletRequest;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
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.Context;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.util.Optional;

@Path("/authorization-servers/{authorizationServerId}/par")
public class OAuth2ParEndpointResource {
   private final Provider<OAuth2ParEndpointService> parEndpointService;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;

   @Inject
   public OAuth2ParEndpointResource(
      Provider<OAuth2ParEndpointService> parEndpointService, Provider<AuthorizationServerIdValidator> authorizationServerIdValidator
   ) {
      this.parEndpointService = parEndpointService;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "201",
               description = "The request was processed successfully and the response contains the <a href=\"https://datatracker.ietf.org/doc/html/rfc9126#name-successful-response\" target=\"_blank\">PAR\nresponse</a>\n",
               content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2ParData.class))
            ),
            @ApiResponse(
               responseCode = "400",
               description = "The request could not be validated. The <a href=\"https://datatracker.ietf.org/doc/html/rfc9126#section-2.3\" target=\"_blank\">error response</a> contains more details",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse")),
                     @Content(mediaType = "application/json", schema = @Schema(ref = "#/components/schemas/OAuth2ErrorResponse"))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "The PAR endpoint is not configured for the specified authorization server",
               content = {
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse")),
                     @Content(mediaType = "application/json", schema = @Schema(ref = "#/components/schemas/OAuth2ErrorResponse"))
               }
            )
      }
   )
   @POST
   @Operation(
      description = "The endpoint used by the client to start a Pushed Authorization Requests (PAR).\nSee also <a href=\"https://datatracker.ietf.org/doc/html/rfc9126\" target=\"_blank\">RFC 9126, OAuth 2.0 Pushed Authorization Requests</a>",
      tags = "OAuth 2.0/OIDC",
      summary = "Start PAR",
      requestBody = @RequestBody(
         description = "A PAR request according to <a href=\"https://datatracker.ietf.org/doc/html/rfc9126\" target=\"_blank\">RFC 9126, OAuth 2.0 Pushed Authorization Requests</a>",
         required = true,
         content = @Content(
            mediaType = "application/x-www-form-urlencoded",
            schema = @Schema(additionalProperties = AdditionalPropertiesValue.TRUE, type = "object")
         )
      )
   )
   @Consumes("application/x-www-form-urlencoded")
   @Produces("application/json")
   @DisableCsrfFilter
   public Response parEndpoint(
      @Parameter(description = "The authorization server ID") @PathParam("authorizationServerId") String authorizationServerId,
      @FormParam("client_id") String clientId,
      @Context HttpServletRequest servletRequest,
      MultivaluedMap<String, String> authorizationParameters
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      return ((OAuth2ParEndpointService)this.parEndpointService.get()).isParDisabled(AuthorizationServerId.authorizationServerId(authorizationServerId))
         ? this.parDisabled()
         : ((OAuth2ParEndpointService)this.parEndpointService.get())
            .handleParRequest(
               new FormParamHttpServletRequestWrapper(servletRequest, authorizationParameters),
               AuthorizationServerId.authorizationServerId(authorizationServerId),
               Optional.ofNullable(clientId).map(OAuth2ClientId::oAuth2ClientId),
               authorizationParameters
            )
            .mapResult(this::parSuccess)
            .orMapFailure(this::parFailure);
   }

   private Response parDisabled() {
      return Response.status(Status.NOT_FOUND).build();
   }

   private Response parSuccess(OAuth2ParResponse parResponse) {
      return Response.status(Status.CREATED).entity(new OAuth2ParData(parResponse.requestUri().requestUri(), parResponse.expiresIn().getSeconds())).build();
   }

   private Response parFailure(OAuth2ParError parError) {
      return Response.status(Status.BAD_REQUEST).entity(new OAuth2ErrorData(parError.oAuth2Error().getParameterValue(), parError.description())).build();
   }
}
