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

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.infrastructure.error.ErrorResponse;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.infrastructure.exception.IamRestErrorCode;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.oauth2.application.service.OAuth2ExtendedSessionService;
import com.airlock.iam.oauth2.domain.model.credential.OAuth2AccessInfo;
import com.airlock.iam.oauth2.domain.model.session.OAuth2ExtendedSession;
import com.airlock.iam.oauth2.domain.model.session.OAuth2SessionId;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2ValidationResult;
import com.airlock.iam.oauth2.infrastructure.validation.AuthorizationServerIdValidator;
import com.airlock.iam.oauth2.infrastructure.validation.OAuth2SessionManagementAccess;
import com.airlock.iam.oauth2.infrastructure.validation.availability.OAuth2SessionManagementEndpointAvailability;
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.http.HttpServletRequest;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import java.util.Collection;

@Path("/authorization-servers/{authorizationServerId}/sessions/{sessionId}")
public class OAuth2SessionResource {
   private final Provider<OAuth2SessionManagementEndpointAvailability> sessionManagementEndpointAvailability;
   private final Provider<OAuth2SessionManagementAccess> accessValidator;
   private final Provider<OAuth2ExtendedSessionService> sessionService;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;
   private final Provider<HttpServletRequest> request;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public OAuth2SessionResource(
      Provider<OAuth2SessionManagementEndpointAvailability> sessionManagementEndpointAvailability,
      Provider<OAuth2SessionManagementAccess> accessValidator,
      Provider<OAuth2ExtendedSessionService> sessionService,
      Provider<AuthorizationServerIdValidator> authorizationServerIdValidator,
      Provider<HttpServletRequest> request,
      HelpdeskLogger helpdeskLogger
   ) {
      this.sessionManagementEndpointAvailability = sessionManagementEndpointAvailability;
      this.accessValidator = accessValidator;
      this.sessionService = sessionService;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
      this.request = request;
      this.helpdeskLogger = helpdeskLogger;
   }

   @ApiResponses(
      {
            @ApiResponse(responseCode = "204", description = "The session has been deleted."),
            @ApiResponse(
               responseCode = "401",
               description = "The access token is missing or invalid.",
               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 = "<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<tr>\n<td>SESSION_NOT_FOUND</td>\n<td>The user of the given access token does not have a session with the specified ID.</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 = "#/components/schemas/OAuth2ErrorResponse"))
               }
            ),
            @ApiResponse(responseCode = "501", description = "The session management endpoint is not configured.")
      }
   )
   @DELETE
   @Operation(
      description = "Deletes the specified OAuth 2.0 session of a user for a given access token. An access token in the bearer token\nformat must be provided as authorization.",
      tags = "OAuth 2.0/OIDC",
      summary = "Delete session"
   )
   @Produces({"application/vnd.api+json", "application/json;qs=0.9"})
   public Response deleteSession(
      @Parameter(description = "The authorization server ID.") @PathParam("authorizationServerId") String authorizationServerId,
      @Parameter(description = "The session ID.") @PathParam("sessionId") @Size(min = 1, max = 1000) String sessionId
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      if (!((OAuth2SessionManagementEndpointAvailability)this.sessionManagementEndpointAvailability.get()).isAvailable()) {
         return ErrorResponse.notImplemented().build().toResponse();
      }

      Result<OAuth2AccessInfo, OAuth2ValidationResult> accessResult = ((OAuth2SessionManagementAccess)this.accessValidator.get())
         .validateAccess((HttpServletRequest)this.request.get());
      if (accessResult.isFailure()) {
         return ErrorResponse.unauthorized().build().toResponse();
      }

      OAuth2AccessInfo accessInfo = accessResult.get();
      UserId userId = UserId.userId(accessInfo.getValidUser().getName());
      Collection<OAuth2ExtendedSession> userSessions = ((OAuth2ExtendedSessionService)this.sessionService.get()).findOAuth2ExtendedSessions(userId);
      OAuth2SessionId sessionToDelete = OAuth2SessionId.oAuth2SessionId(sessionId);
      if (userSessions.stream().noneMatch(s -> s.id().equals(sessionToDelete))) {
         return ErrorResponse.notFound().errorCode(IamRestErrorCode.SESSION_NOT_FOUND).build().toResponse();
      }

      if (!((OAuth2ExtendedSessionService)this.sessionService.get()).deleteOAuth2Session(sessionToDelete)) {
         return ErrorResponse.notFound().errorCode(IamRestErrorCode.SESSION_NOT_FOUND).build().toResponse();
      }

      this.helpdeskLogger
         .log(
            userId,
            "OAuth 2.0 session "
               + LogUtil.quote(sessionToDelete)
               + " was deleted using an access token of client "
               + LogUtil.logSafeQuote(accessInfo.getAccessToken().getClientId())
         );
      return Response.noContent().build();
   }
}
