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

import com.airlock.iam.common.api.infrastructure.annotation.DisableCsrfFilter;
import com.airlock.iam.common.api.infrastructure.annotation.RequiresWritableSession;
import com.airlock.iam.common.infrastructure.response.CacheControlResponseHeaderService;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.application.service.metadata.OpenIdConnectDiscoveryService;
import com.airlock.iam.oauth2.domain.model.metadata.OAuth2MetadataIssuerValidationException;
import com.airlock.iam.oauth2.infrastructure.transfer.documentation.OAuth2DiscoveryAndMetadataEndpointResponseData;
import com.airlock.iam.oauth2.infrastructure.validation.AuthorizationServerIdValidator;
import com.airlock.iam.oauth2.infrastructure.validation.availability.OpenIdConnectDiscoveryEndpointAvailability;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
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.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.Response.Status;

@Path("/authorization-servers/{authorizationServerId}/.well-known/openid-configuration")
public class OAuth2DiscoveryEndpointResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2DiscoveryEndpointResource.class);
   private final Provider<OpenIdConnectDiscoveryEndpointAvailability> discoveryEndpointAvailability;
   private final Provider<OpenIdConnectDiscoveryService> discoveryService;
   private final Provider<CacheControlResponseHeaderService> cacheControlResponseHeaderService;
   private final Provider<AuthorizationServerIdValidator> authorizationServerIdValidator;

   @Inject
   public OAuth2DiscoveryEndpointResource(
      Provider<OpenIdConnectDiscoveryEndpointAvailability> discoveryEndpointAvailability,
      Provider<OpenIdConnectDiscoveryService> discoveryService,
      Provider<CacheControlResponseHeaderService> cacheControlResponseHeaderService,
      Provider<AuthorizationServerIdValidator> authorizationServerIdValidator
   ) {
      this.discoveryEndpointAvailability = discoveryEndpointAvailability;
      this.discoveryService = discoveryService;
      this.cacheControlResponseHeaderService = cacheControlResponseHeaderService;
      this.authorizationServerIdValidator = authorizationServerIdValidator;
   }

   @ApiResponses(
      {
            @ApiResponse(
               responseCode = "200",
               description = "Retrieved the discovery information.",
               content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2DiscoveryAndMetadataEndpointResponseData.class))
            ),
            @ApiResponse(
               responseCode = "404",
               description = "<div class=\"iam-error-code\"><table>\n<tr>\n<td>-</td>\n<td>No discovery endpoint available.</td>\n</tr>\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"
            ),
            @ApiResponse(responseCode = "500", description = "The discovery endpoint is not configured correctly.")
      }
   )
   @GET
   @Operation(
      description = "Returns the OpenID Connect Discovery information about this authorization server.\nRefer to the <a href=\"https://openid.net/specs/openid-connect-discovery-1_0.html\" target=\"_blank\">OpenID Connect Discovery</a> specification\nfor a more detailed description of the endpoint.\n<div class=\"iam-resource-return\">The set of claims about the OpenID Provider's configuration.\n Depending on the supported specifications, other claims may also be returned.</div>",
      tags = "OAuth 2.0/OIDC",
      summary = "Get discovery info"
   )
   @Produces("application/json")
   @DisableCsrfFilter
   @RequiresWritableSession
   public Response discoveryEndpoint(
      @Context ContainerRequestContext requestContext,
      @Parameter(description = "The authorization server ID.") @PathParam("authorizationServerId") String authorizationServerId
   ) {
      ((AuthorizationServerIdValidator)this.authorizationServerIdValidator.get()).validateAuthorizationServer(authorizationServerId);
      if (!((OpenIdConnectDiscoveryEndpointAvailability)this.discoveryEndpointAvailability.get()).isAvailable()) {
         LOG.info("No Discovery resource available because there is no OpenID Connect Grant configured.");
         return Response.status(Status.NOT_FOUND).build();
      }

      try {
         OIDCProviderMetadata metadata = ((OpenIdConnectDiscoveryService)this.discoveryService.get()).provideDiscoveryMetadata();
         return ((CacheControlResponseHeaderService)this.cacheControlResponseHeaderService.get())
            .setCacheControlHeader(defaultResponse(metadata), requestContext)
            .build();
      } catch (OAuth2MetadataIssuerValidationException e) {
         return Response.serverError().build();
      }
   }

   private static ResponseBuilder defaultResponse(OIDCProviderMetadata metadata) {
      return Response.ok(metadata.toJSONObject().toJSONString());
   }
}
