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

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.saml2.application.service.sp.Saml2SpInitiatedSloService;
import com.airlock.iam.saml2.domain.model.sp.logout.Saml2SpInitiatedSloId;
import com.airlock.iam.saml2.infrastructure.service.sp.logout.Saml2IdpInitiatedSloServiceImpl;
import com.airlock.iam.saml2.infrastructure.util.Saml2RequestThreadContext;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.FormParam;
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.QueryParam;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.net.URI;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

@Path("/sp/slo")
public final class Saml2SpSloResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2SpSloResource.class);
   private final Provider<Saml2IdpInitiatedSloServiceImpl> idpInitiatedSloService;
   private final Provider<Saml2SpInitiatedSloService> spInitiatedSloService;

   @Inject
   Saml2SpSloResource(Provider<Saml2IdpInitiatedSloServiceImpl> idpInitiatedSloService, Provider<Saml2SpInitiatedSloService> spInitiatedSloService) {
      this.idpInitiatedSloService = idpInitiatedSloService;
      this.spInitiatedSloService = spInitiatedSloService;
   }

   @Path("metaAlias/{metaAlias}")
   @GET
   @Produces("text/html")
   @RequiresWritableSession
   public Response sloRequestByGet(
      @NotNull(message = "{metaAlias.notNull}") @Size(min = 1, max = 100, message = "{metaAlias.size}") @Pattern(
   regexp = "[a-zA-Z0-9][a-zA-Z0-9-_.]{1,99}",
   message = "{metaAlias.pattern}"
) @PathParam("metaAlias") String metaAlias,
      @QueryParam("SAMLRequest") String samlRequest,
      @QueryParam("SAMLResponse") String samlResponse
   ) {
      if (StringUtils.isNotBlank(samlRequest) == StringUtils.isNotBlank(samlResponse)) {
         throw new ViolationException("Request must contain either a SAMLRequest or a SAMLResponse");
      } else {
         return StringUtils.isNotBlank(samlRequest) ? this.handleIdpInitiatedSloRequest() : this.handleSpInitiatedSloResponse();
      }
   }

   @Path("metaAlias/{metaAlias}")
   @POST
   @Produces("text/html")
   public Response sloRequestByPost(
      @NotNull(message = "{metaAlias.notNull}") @Size(min = 1, max = 100, message = "{metaAlias.size}") @Pattern(
   regexp = "[a-zA-Z0-9][a-zA-Z0-9-_.]{1,99}",
   message = "{metaAlias.pattern}"
) @PathParam("metaAlias") String metaAlias,
      @FormParam("SAMLRequest") String samlRequest,
      @FormParam("SAMLResponse") String samlResponse
   ) {
      if (StringUtils.isNotBlank(samlRequest) == StringUtils.isNotBlank(samlResponse)) {
         throw new ViolationException("Request contained neither SAMLRequest nor SAMLResponse");
      } else {
         return StringUtils.isNotBlank(samlRequest) ? this.handleIdpInitiatedSloRequest() : this.handleSpInitiatedSloResponse();
      }
   }

   @Path("continue")
   @GET
   @Produces("text/html")
   @RequiresWritableSession
   public Response continueIdpInitiatedSlo(
      @NotNull(message = "{id.notNull}") @Size(min = 1, max = 50, message = "{id.size}") @QueryParam("id") String unverifiedSloId
   ) {
      ((Saml2IdpInitiatedSloServiceImpl)this.idpInitiatedSloService.get()).continueIdpInitiatedSlo(unverifiedSloId);
      return this.responseFromLibrary();
   }

   @Path("init")
   @GET
   @Produces("text/html")
   @RequiresWritableSession
   public Response init(
      @NotNull(message = "{id.notNull}") @Size(min = 1, max = 50, message = "{id.size}") @QueryParam("id") String id,
      @NotNull(message = "{location.notNull}") @Size(min = 1, max = 2048, message = "{location.size}") @QueryParam("Location") String uiLogoutResumeUriParameter
   ) {
      Saml2SpInitiatedSloId saml2SpInitiatedSloId = Saml2SpInitiatedSloId.parseSpInitiatedSloId(id);
      URI uiLogoutResumeUri = ((Saml2SpInitiatedSloService)this.spInitiatedSloService.get()).validateAndStoreUiLogoutResumeUri(uiLogoutResumeUriParameter);
      Optional<URI> redirectUri = ((Saml2SpInitiatedSloService)this.spInitiatedSloService.get()).startSpInitiatedSlo(saml2SpInitiatedSloId, uiLogoutResumeUri);
      return this.mapToResponse(redirectUri);
   }

   private Response handleIdpInitiatedSloRequest() {
      URI redirectUri = ((Saml2IdpInitiatedSloServiceImpl)this.idpInitiatedSloService.get()).processIdpInitiatedSlo();
      LOG.info("Redirecting to UI: " + redirectUri.toASCIIString());
      return Response.status(Status.FOUND).header("Location", redirectUri).build();
   }

   private Response handleSpInitiatedSloResponse() {
      URI uiLogoutResumeUri = ((Saml2SpInitiatedSloService)this.spInitiatedSloService.get()).retrieveAndValidateUiLogoutResumeUri();
      ((Saml2SpInitiatedSloService)this.spInitiatedSloService.get()).processSpInitiatedLogoutResponse(uiLogoutResumeUri);
      return Response.status(Status.FOUND).header("Location", uiLogoutResumeUri).build();
   }

   private Response mapToResponse(Optional<URI> redirectUri) {
      return redirectUri.isPresent() ? Response.status(Status.FOUND).header("Location", redirectUri.get()).build() : this.responseFromLibrary();
   }

   private Response responseFromLibrary() {
      return Response.status(Saml2RequestThreadContext.getResponse().getStatus()).build();
   }
}
