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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.api.infrastructure.annotation.DisableCsrfFilter;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.oauth2.infrastructure.validation.AuthorizationServerIdValidator;
import com.airlock.iam.techclientreg.api.domain.model.oauth2.OAuth2ClientRegistrationError;
import com.airlock.iam.techclientreg.api.domain.model.oauth2.OAuth2ClientRegistrationRequest;
import com.airlock.iam.techclientreg.api.infrastructure.transfer.response.OAuth2ClientRegistrationErrorResponseData;
import com.airlock.iam.techclientreg.domain.model.flow.OAuth2ClientRegistration;
import com.airlock.iam.techclientreg.domain.model.registration.OAuth2ClientRegistrationRequestImpl;
import com.airlock.iam.techclientreg.domain.model.step.OAuth2ClientRegistrationResponseData;
import com.airlock.iam.techclientreg.infrastructure.transfer.request.OAuth2ClientRegistrationRequestContainer;
import com.airlock.iam.techclientreg.infrastructure.transfer.response.OAuth2ClientRegistrationData;
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.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
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.validation.Valid;
import jakarta.validation.constraints.NotNull;
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;
import ma.glasnost.orika.MapperFacade;

@Path("oauth2/{authorizationServerId}/register")
public class OAuth2DynamicClientRegistrationResource {
   private static final String CLIENT_REGISTRATION_EXAMPLE = "{\n\t\"scope\" : \"defend_winterfell defend_castle_black attack_winterfell\",\n\t\"contacts\" : [\"admin@winterfell.test\",\"john@winterfell.test\"],\n\t\"redirect_uris\" : [\"https://winterfell.test/oauth\"],\n\t\"token_endpoint_auth_method\" : \"client_secret_basic\",\n\t\"client_name\" : \"throne_client\",\n\t\"client_uri\" : \"https://winterfell.test/client\",\n\t\"logo_uri\" : \"https://winterfell.test/logo\",\n\t\"tos_uri\" : \"https://winterfell.test/terms-of-service\",\n\t\"policy_uri\" : \"https://winterfell.test/privacy-policy\",\n\t\"software_id\" : \"OAuth2DynamicClientRegistrationRfcTest\",\n\t\"software_version\" : \"1.0\",\n\t\"requested_client_id\" : \"myClient\",\n\t\"client_name#de\" : \"Kundenname\",\n\t\"client_name#zh-CN\" : \"glurz\",\n\t\"client_name#ja-Jpan-JP\" : \"RFC Example\",\n\t\"client_uri#de\" : \"https://client_uri.de\",\n\t\"policy_uri#zh-CN\" : \"https://policy_uri.cn\",\n\t\"tos_uri#de\" : \"https://tos_uri.de\",\n\t\"logo_uri#zh-CN\" : \"https://logo_uri.cn\"}\n";
   private final MapperFacade mapper;
   private final Provider<OAuth2ClientRegistration> registrationStep;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;

   @Inject
   public OAuth2DynamicClientRegistrationResource(
      MapperFacade mapper, Provider<OAuth2ClientRegistration> registration, Provider<AuthorizationServerIdValidator> authorizationServerIdValidator
   ) {
      this.mapper = mapper;
      this.registrationStep = registration;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "201",
               description = "Client successfully registered.",
               content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2ClientRegistrationData.class))
            ),
            @ApiResponse(
               responseCode = "400",
               description = "<p>The request was invalid.</p>\n<p>Formatted according to RFC 7591 section 3.2.2 if the error situation is covered by the RFC.</p>\n<p>General JSON Api error in case of a general request validation error which is not covered by the RFC.</p>",
               content = {
                     @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2ClientRegistrationErrorResponseData.class)),
                     @Content(mediaType = "application/vnd.api+json", schema = @Schema(ref = "json-api.yaml#/components/schemas/ErrorDocumentResponse"))
               }
            ),
            @ApiResponse(
               responseCode = "404",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>AUTHORIZATION_SERVER_NOT_FOUND</td>\n<td>The authorization server ID does not exist.</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 = "501",
               description = "The specified authorization server does not support dynamic client registration.",
               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"))
               }
            )
      }
   )
   @DisableCsrfFilter
   @Operation(
      description = "Registers an OAuth 2.0 technical client.\n<p>This endpoint is implemented in accordance with <a href=\"https://tools.ietf.org/html/rfc7591\">RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol</a>.</p>\n<p>To be RFC-compliant, this endpoint does not require the CSRF protection header <tt>X-Same-Domain</tt>.\nRequests to this endpoint are guaranteed to be non-simple, because of the enforced non-simple content type\n<tt>application/json</tt>.</p>\n<div class=\"iam-resource-return\">Whether the registration was successful and if so, includes what metadata were registered.\n<p>\nIf an error occurs that is covered by RFC 7591, the error format described in section 3.2.2 is returned:\n<pre><code class=\"language-typescript\">{\n\terror: string // mandatory, possible values: invalid_redirect_uri, invalid_client_metadata, invalid_software_statement or unapproved_software_statement\n\terror_description: string // optional, textual description. No API.\n}</code></pre>\n</p>\n<p>\nOther errors (such as infrastructure failures or syntactically invalid requests) that are not covered by the RFC are handled\nin the default error response format as described in <a href=\"#errorResponseFormat\">Error Responses</a>.\n</p>\nThis endpoint is implemented as a flow which allows non-interactive steps to be placed before or after the registration step. Interactive steps\nwould violate the RFC. Please take note of the following differences to other flow endpoints:\n<ul>\n<li>Responses with status code 400 also terminate the flow. There's no concept of a retry.</li>\n<li>The format of a successful response is specified by the RFC and does not inform about a next step.</li>\n</ul></div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Register client"
   )
   @POST
   @Consumes("application/json")
   @Produces("application/json")
   public Response register(
      @Parameter(description = "Contains the client metadata to register.") @RequestBody(
   content = @Content(
      mediaType = "application/json",
      examples = @ExampleObject(
         name = "Client registration",
         value = "{\n\t\"scope\" : \"defend_winterfell defend_castle_black attack_winterfell\",\n\t\"contacts\" : [\"admin@winterfell.test\",\"john@winterfell.test\"],\n\t\"redirect_uris\" : [\"https://winterfell.test/oauth\"],\n\t\"token_endpoint_auth_method\" : \"client_secret_basic\",\n\t\"client_name\" : \"throne_client\",\n\t\"client_uri\" : \"https://winterfell.test/client\",\n\t\"logo_uri\" : \"https://winterfell.test/logo\",\n\t\"tos_uri\" : \"https://winterfell.test/terms-of-service\",\n\t\"policy_uri\" : \"https://winterfell.test/privacy-policy\",\n\t\"software_id\" : \"OAuth2DynamicClientRegistrationRfcTest\",\n\t\"software_version\" : \"1.0\",\n\t\"requested_client_id\" : \"myClient\",\n\t\"client_name#de\" : \"Kundenname\",\n\t\"client_name#zh-CN\" : \"glurz\",\n\t\"client_name#ja-Jpan-JP\" : \"RFC Example\",\n\t\"client_uri#de\" : \"https://client_uri.de\",\n\t\"policy_uri#zh-CN\" : \"https://policy_uri.cn\",\n\t\"tos_uri#de\" : \"https://tos_uri.de\",\n\t\"logo_uri#zh-CN\" : \"https://logo_uri.cn\"}\n"
      )
   )
) @NotNull @Valid OAuth2ClientRegistrationRequestContainer request,
      @Parameter(description = "The identifier of the authorization server to register with.") @PathParam("authorizationServerId") String authorizationServerId
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      OAuth2ClientRegistrationRequest registrationRequest = (OAuth2ClientRegistrationRequest)this.mapper
         .map(request, OAuth2ClientRegistrationRequestImpl.class);
      FlowResult result = ((OAuth2ClientRegistration)this.registrationStep.get()).register(registrationRequest);
      if (result.isFailed()) {
         Optional<OAuth2ClientRegistrationError> resultData = result.data(OAuth2ClientRegistrationError.class);
         if (resultData.isPresent()) {
            return Response.status(Status.BAD_REQUEST).entity(OAuth2ClientRegistrationErrorResponseData.clientRegError(resultData.get())).build();
         } else {
            return result.error().filter(GenericStepError.NOT_IMPLEMENTED::equals).isPresent()
               ? Response.status(Status.NOT_IMPLEMENTED).build()
               : Response.status(Status.BAD_REQUEST)
                  .entity(OAuth2ClientRegistrationErrorResponseData.clientRegError(OAuth2ClientRegistrationError.invalidClientMetadata(null)))
                  .build();
         }
      } else {
         OAuth2ClientRegistrationResponseData registrationResponse = result.data(OAuth2ClientRegistrationResponseData.class)
            .orElseThrow(() -> new FailureException("Flow result must contain " + OAuth2ClientRegistrationResponseData.class.getSimpleName()));
         OAuth2ClientRegistrationData data = (OAuth2ClientRegistrationData)this.mapper.map(registrationResponse, OAuth2ClientRegistrationData.class);
         return Response.status(Status.CREATED).entity(data).build();
      }
   }
}
