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

import com.airlock.iam.authentication.infrastructure.generated.transfer.response.OAuth2LocalConsentAttributeDataResourceDocument;
import com.airlock.iam.authentication.infrastructure.transfer.request.OAuth2CheckConsentRequest;
import com.airlock.iam.authentication.infrastructure.transfer.request.OAuth2LocalConsentGrantRequest;
import com.airlock.iam.authentication.infrastructure.transfer.response.OAuth2LocalConsentAttributeData;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.infrastructure.mapper.FlowResultMapper;
import com.airlock.iam.flow.auth.api.infrastructure.generated.transfer.response.AuthenticationFlowResultDataResourceDocument;
import com.airlock.iam.flow.infrastructure.mapper.ValidationResultMapper;
import com.airlock.iam.oauth2.domain.model.provider.OAuth2Consent;
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.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;

@Path("/oauth2/authorization-server/consent")
public class OAuth2ConsentFlowResource {
   private final FlowResultMapper mapper;
   private final Provider<OAuth2Consent> consent;
   private final Provider<ValidationResultMapper> stepErrorResultMapper;

   @Inject
   public OAuth2ConsentFlowResource(FlowResultMapper mapper, Provider<OAuth2Consent> consent, Provider<ValidationResultMapper> stepErrorResultMapper) {
      this.mapper = mapper;
      this.consent = consent;
      this.stepErrorResultMapper = stepErrorResultMapper;
   }

   @ApiResponse(
      responseCode = "200",
      description = "Retrieved consent data.\n<br>Possible next steps: <tt>OAUTH2_AUTHORIZATION_SERVER_LOCAL_CONSENT_REQUIRED</tt> (as documented <a href=\"#nextAuthStepCodes\">here</a>).\n",
      content = {
            @Content(mediaType = "application/vnd.api+json", schema = @Schema(implementation = OAuth2LocalConsentAttributeDataResourceDocument.class)),
            @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2LocalConsentAttributeDataResourceDocument.class))
      }
   )
   @Operation(
      description = "Retrieves the OAuth 2.0 local consent data.\n<div class=\"iam-resource-return\">The consent data consisting of client information and grantable scopes.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Get local consent data"
   )
   @POST
   @Path("/local/retrieve")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response retrieve() {
      FlowResult result = ((OAuth2Consent)this.consent.get()).retrieveLocalConsent();
      return this.mapper.toResponseWithData(result, OAuth2LocalConsentAttributeData.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 = "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</table>\n</div>\n<br>Possible next step: <tt>OAUTH2_AUTHORIZATION_SERVER_LOCAL_CONSENT_REQUIRED</tt> (as documented <a href=\"#nextAuthStepCodes\">here</a>).\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 = "Grants the submitted OAuth 2.0 scopes.\n<p>Notice that not all available scopes have to be granted by the user. Scopes not granted are automatically treated as denied.</p>\n<p>If consent storage is configured, the granted (and implicitly denied) scopes are stored in the database.</p>\n<div class=\"iam-resource-return\">Whether the scopes were successfully granted. If the attribute <tt>nextAuthStep</tt> is present in the response,\n further steps are required to successfully authenticate. If the attribute is missing, the user is\n successfully authenticated.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Grant scopes"
   )
   @POST
   @Path("/local/grant")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response grant(
      @Parameter(description = "Request containing the scopes to grant. Denied scopes are not included.") @NotNull @Valid OAuth2LocalConsentGrantRequest request
   ) {
      FlowResult result = ((OAuth2Consent)this.consent.get()).grantLocalConsent(request.getScopes());
      return ((ValidationResultMapper)this.stepErrorResultMapper.get()).toValidationResponse(result);
   }

   @ApiResponse(
      responseCode = "403",
      description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>OAUTH2_AUTHORIZATION_SERVER_AUTHORIZATION_FAILED</td>\n<td>OAuth 2.0 authorization failed. The current authentication flow has been aborted.</td>\n</tr>\n</table>\n</div>\n<br>\nThe browser should be redirected to this URI pointing back to the OAuth 2.0/OpenID Connect client contained in\nthe additional attribute <tt>clientRedirectUri</tt>.\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 = "Denies the OAuth 2.0 consent and therefore aborts the authentication flow to redirect back to the client with an OAuth 2.0 error code.\n<p>Notice that denying the consent will not store or update any granted/denied consents.</p>\n<div class=\"iam-resource-return\">The response containing the <tt>clientRedirectUri</tt> attribute.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Deny consent"
   )
   @POST
   @Path("/local/deny")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response deny() {
      return this.mapper.toResponse(((OAuth2Consent)this.consent.get()).denyLocalConsent());
   }

   @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>OAUTH2_AUTHORIZATION_SERVER_AUTHORIZATION_FAILED</td>\n<td>OAuth 2.0 authorization failed. The current authentication flow has been aborted.</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"))
               }
            )
      }
   )
   @Operation(
      description = "Checks if the given consent token is valid.\n<div class=\"iam-resource-return\">Whether the scopes were successfully granted. If the attribute <tt>nextAuthStep</tt> is present in the response,\n further steps are required to successfully authenticate. If the attribute is missing, the user is\n successfully authenticated.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Check token"
   )
   @POST
   @Path("/remote/check")
   @Consumes("application/json")
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response check(@Parameter(description = "Request containing the consent token.") @NotNull @Valid OAuth2CheckConsentRequest request) {
      return this.mapper.toResponse(((OAuth2Consent)this.consent.get()).checkRemoteConsent(request.getConsentToken()));
   }
}
