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

import com.airlock.iam.common.api.infrastructure.annotation.DisableCsrfFilter;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.application.service.introspection.TokenIntrospectionAuthService;
import com.airlock.iam.oauth2.application.service.introspection.TokenIntrospectionResult;
import com.airlock.iam.oauth2.application.service.introspection.TokenIntrospectionService;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2ValidationResult;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2ValidationStatus;
import com.airlock.iam.oauth2.infrastructure.mapper.TokenIntrospectionResultMapper;
import com.airlock.iam.oauth2.infrastructure.transfer.documentation.OAuth2TokenIntrospectionResponseData;
import com.airlock.iam.oauth2.infrastructure.validation.AuthorizationServerIdValidator;
import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse;
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.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.Response;
import jakarta.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;

@Path("/authorization-servers/{authorizationServerId}/introspect")
public class OAuth2TokenIntrospectionResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2TokenIntrospectionResource.class);
   private final Provider<TokenIntrospectionAuthService> tokenIntrospectionAuthService;
   private final Provider<TokenIntrospectionService> introspectionService;
   private final HttpServletRequest request;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;

   @Inject
   public OAuth2TokenIntrospectionResource(
      Provider<TokenIntrospectionAuthService> tokenIntrospectionAuthService,
      Provider<TokenIntrospectionService> introspectionService,
      HttpServletRequest request,
      Provider<AuthorizationServerIdValidator> authorizationServerIdValidator
   ) {
      this.tokenIntrospectionAuthService = tokenIntrospectionAuthService;
      this.introspectionService = introspectionService;
      this.request = request;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Retrieved the token metadata.",
               content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2TokenIntrospectionResponseData.class))
            ),
            @ApiResponse(
               responseCode = "401",
               description = "Invalid or missing basic authentication credentials.",
               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</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"))
               }
            )
      }
   )
   @POST
   @Operation(
      description = "Introspects the specified token. Authentication must be performed with basic authentication using the explicitly\nconfigured credentials. Refer to the <a href=\"https://tools.ietf.org/html/rfc7662\" target=\"_blank\">RFC 7662</a> specification\nfor a more detailed description of the endpoint.\n<div class=\"iam-resource-return\">The metadata of the given token.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Introspect token"
   )
   @Consumes("application/x-www-form-urlencoded")
   @Produces("application/json")
   @DisableCsrfFilter
   public Response introspect(
      @Parameter(description = "The authorization server ID.") @PathParam("authorizationServerId") String authorizationServerId,
      @Parameter(description = "The access or refresh token (mandatory).") @FormParam("token") @Size(min = 1, max = 10000) String token,
      @Parameter(description = "The token type hint.") @FormParam("token_type_hint") @Size(max = 1000) String tokenTypeHint
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      if (!this.validateAuthentication()) {
         LOG.info("No or wrong basic auth credentials provided.");
         return OAuth2ErrorResponse.map(new OAuth2ValidationResult(OAuth2ValidationStatus.BASIC_AUTH_NOT_VALID)).build();
      }

      if (StringUtils.isBlank(token)) {
         return Response.status(Status.BAD_REQUEST).build();
      }

      TokenIntrospectionResult result = ((TokenIntrospectionService)this.introspectionService.get()).introspect(token);
      TokenIntrospectionSuccessResponse res = TokenIntrospectionResultMapper.mapTokenIntrospectionResponse(result);
      return Response.ok(res.toJSONObject().toJSONString()).build();
   }

   private boolean validateAuthentication() {
      String header = this.extractBasicAuthHeader();
      return ((TokenIntrospectionAuthService)this.tokenIntrospectionAuthService.get()).isCredentialValid(header);
   }

   private String extractBasicAuthHeader() {
      return this.request.getHeader("Authorization");
   }
}
