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

import com.airlock.iam.authentication.application.service.oauth2.OAuth2AuthorizationService;
import com.airlock.iam.authentication.application.service.oauth2.OpenIdConnectPromptNoneAuthorizationService;
import com.airlock.iam.authentication.infrastructure.transfer.request.OAuth2AuthorizationRequest;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.common.api.infrastructure.annotation.DisableCsrfFilter;
import com.airlock.iam.common.api.infrastructure.annotation.DisableResponseDelay;
import com.airlock.iam.common.api.infrastructure.annotation.RequiresWritableSession;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
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.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.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.ServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
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;

@Path("/oauth2/authorization-servers/{authorizationServerId}/authorize")
public final class ApplicationAccessByOAuth2AuthorizationResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(ApplicationAccessByOAuth2AuthorizationResource.class);
   private final Provider<OAuth2AuthorizationService> oAuth2AuthorizationService;
   private final Provider<FlowResultMapper> mapper;
   private final Provider<OpenIdConnectPromptNoneAuthorizationService> promptNoneService;
   private final Provider<ExternalUriInfo> uriInfo;
   private final Provider<ServletRequest> servletRequest;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;

   @Inject
   public ApplicationAccessByOAuth2AuthorizationResource(
      Provider<FlowResultMapper> mapper,
      Provider<OAuth2AuthorizationService> oAuth2AuthorizationService,
      Provider<OpenIdConnectPromptNoneAuthorizationService> promptNoneService,
      Provider<ExternalUriInfo> uriInfo,
      Provider<ServletRequest> servletRequest,
      Provider<AuthorizationServerIdValidator> authorizationServerIdValidator
   ) {
      this.mapper = mapper;
      this.oAuth2AuthorizationService = oAuth2AuthorizationService;
      this.promptNoneService = promptNoneService;
      this.uriInfo = uriInfo;
      this.servletRequest = servletRequest;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Authorization is complete. The response contains the <tt>X-Forward-URL</tt> header with the URI pointing\nback to the OAuth 2.0/OpenID Connect client with the authorization response.\n",
               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>UNEXPECTED_CALL</td>\n<td>A flow is already in progress. To start a new flow, the running flow must first be terminated.</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 = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>NOT_AUTHORIZED</td>\n<td>Access not yet granted, more authentication/authorization steps are required.</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 = "403",
               description = "Access to this target application cannot be granted.<br>\n<div class=\"iam-error-code\"><table>\n\t<tr>\n\t\t<td>OAUTH2_AUTHORIZATION_SERVER_INVALID_AUTHORIZATION_REQUEST</td>\n\t\t<td>The OAuth 2.0/OpenID Connect authorization request could not be validated.<br>\n\t\tIn some cases, the response contains an additional attribute <tt>clientRedirectUri</tt>.\n\t\tIf present, the browser should be redirected to this URI pointing back to the OAuth 2.0/OpenID Connect client,\n\t\twhich also contains some information about the error.</td>\n\t</tr>\n\t<tr>\n\t\t<td>OAUTH2_AUTHORIZATION_SERVER_REAUTHENTICATION_REQUIRED</td>\n\t\t<td>The OpenID Connect authentication request requires the user to be re-authenticated. The current user\n\t\tsession has to be deauthenticated (using <span class=\"httpMethod small DELETE\">DELETE</span><tt>\n\t\t/public/authentication/</tt>) first, before calling this endpoint with this OpenID Connect authentication\n\t\trequest.</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"))
               }
            ),
            @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"))
               }
            )
      }
   )
   @Operation(
      description = "Initiates an OAuth 2.0 Authorization Code Grant/OpenID Connect Authorization Code Flow given the original query string.\nIf successful, the corresponding authentication flow is started. If not, a redirect URI back to the client\nmay be returned with an error.\n<p>\nRetrieving information about ongoing flows can be done by calling\n<tt><span class=\"httpMethod small GET\">GET</span>/public/flow/</tt>.\n</p>\n<div class=\"iam-resource-return\">A success response without next step if the request was validated and the user is authorized to access the\n OAuth 2.0/OpenID Connect client or an error response if further steps are required.<br>\n For OpenID Connect authentication requests containing UI locales, the locales are matched against the configured\n locales. The first matching and therefore valid locale will be reflected in the attribute <tt>uiLocale</tt>\n in the response. The attribute <tt>uiLocale</tt> is missing if either no UI locales were requested or none of\n the requested locales are valid, hence the UI should keep its current locale.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Initiate OAuth flow"
   )
   @POST
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   @Consumes("application/json")
   @DisableResponseDelay
   public Response access(
      @Parameter(description = "The authorization server ID.") @PathParam("authorizationServerId") String authorizationServerId,
      @Parameter(description = "Contains the query string containing all authorization parameters") @NotNull @Valid OAuth2AuthorizationRequest request
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      FlowResult result = ((OAuth2AuthorizationService)this.oAuth2AuthorizationService.get()).access(request.getQueryString());
      return ((FlowResultMapper)this.mapper.get()).toResponse(result);
   }

   @GET
   @DisableResponseDelay
   @DisableCsrfFilter
   @RequiresWritableSession
   public Response accessWithPromptNone(@PathParam("authorizationServerId") String authorizationServerId) {
      if (((ServletRequest)this.servletRequest.get()).getDispatcherType() != DispatcherType.FORWARD) {
         LOG.info("Authorization endpoint for 'prompt=none' cannot be called directly, returning 'not found'.");
         return Response.status(Status.NOT_FOUND).build();
      } else {
         return ((OpenIdConnectPromptNoneAuthorizationService)this.promptNoneService.get())
            .accessWithPromptNone(((ExternalUriInfo)this.uriInfo.get()).getRequestUri())
            .map(location -> Response.status(Status.FOUND).location(location).type("text/plain"))
            .orElseGet(Response::serverError)
            .build();
      }
   }
}
