package com.airlock.iam.saml2.infrastructure.service.idp.logout;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.saml2.application.service.idp.Saml2SpInitiatedResumeSloService;
import com.airlock.iam.saml2.domain.model.idp.logout.Saml2SpInitiatedSloAttributes;
import com.airlock.iam.saml2.domain.model.idp.logout.Saml2SpInitiatedSloId;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.infrastructure.idp.logout.Saml2SpInitiatedSloCache;
import com.airlock.iam.saml2.infrastructure.request.RebuiltInitialSpInitiatedSloRequest;
import com.airlock.iam.saml2.infrastructure.request.Saml2Request;
import com.airlock.iam.saml2.infrastructure.service.idp.Saml2IdpFederationLibrary;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.servlet.http.HttpServletRequest;

public final class Saml2SpInitiatedResumeSloServiceImpl implements Saml2SpInitiatedResumeSloService {
   private final Provider<Saml2SpInitiatedSloCache> spInitiatedSloCache;
   private final Provider<FlowFederationSessionRepository> federationSessionRepository;
   private final Provider<Saml2IdpFederationLibrary> library;
   private final Provider<Saml2Request> request;

   @Inject
   Saml2SpInitiatedResumeSloServiceImpl(
      Provider<Saml2SpInitiatedSloCache> spInitiatedSloCache,
      Provider<FlowFederationSessionRepository> federationSessionRepository,
      Provider<Saml2IdpFederationLibrary> library,
      Provider<Saml2Request> request
   ) {
      this.spInitiatedSloCache = spInitiatedSloCache;
      this.federationSessionRepository = federationSessionRepository;
      this.library = library;
      this.request = request;
   }

   @Override
   public void resume(Saml2SpInitiatedSloId sloId) {
      Saml2SpInitiatedSloCache.Saml2SpInitiatedSloCacheEntry cacheEntry = this.retrieveSloCacheEntry(sloId);
      this.restoreFederationSession(cacheEntry.federationSession());
      this.delegateToLibrary(cacheEntry.attributes());
   }

   private Saml2SpInitiatedSloCache.Saml2SpInitiatedSloCacheEntry retrieveSloCacheEntry(Saml2SpInitiatedSloId sloId) {
      return ((Saml2SpInitiatedSloCache)this.spInitiatedSloCache.get())
         .readOnce(sloId)
         .orElseThrow(
            () -> new ViolationException(
               "Invalid SP-initiated SLO identifier "
                  + LogUtil.logSafeQuote(sloId)
                  + ":  either no SP-initiated SLO process with this ID exists or a timeout occurred."
            )
         );
   }

   private void restoreFederationSession(FlowFederationSession federationSession) {
      if (!federationSession.isSingleLogoutInProgress()) {
         throw new FailureException(
            "Unable to do SP-initiated SLO, either user's FlowFederationSession has not yet been marked for SLO, or SLO has already been performed."
         );
      }

      if (!federationSession.isSpInitiatedSingleLogoutInProgressOnIdp()) {
         throw new FailureException(
            "Request received to do an SP-initiated SLO (single logout) but another type of SLO has already been started on this session before. It looks like a different SLO process has been interrupted (e.g. a SAML SP failed to redirect the client back to us). Continuing logout."
         );
      }

      federationSession.setBlockRestRequests(true);
      ((FlowFederationSessionRepository)this.federationSessionRepository.get()).updateFederationSession(federationSession);
   }

   private void delegateToLibrary(Saml2SpInitiatedSloAttributes sloAttributes) {
      ((Saml2IdpFederationLibrary)this.library.get())
         .resumeSpInitiatedSlo(this.rebuildInitialSpInitiatedSloRequest(sloAttributes), sloAttributes.getSamlSloRequest(), sloAttributes.getRelayState());
   }

   private RebuiltInitialSpInitiatedSloRequest rebuildInitialSpInitiatedSloRequest(Saml2SpInitiatedSloAttributes sloAttributes) {
      RebuiltInitialSpInitiatedSloRequest.RebuiltInitialSpInitiatedSloRequestBuilder builder = RebuiltInitialSpInitiatedSloRequest.builder()
         .request((HttpServletRequest)this.request.get())
         .httpMethod(sloAttributes.getHttpMethod())
         .requestUri(sloAttributes.getRequestUri());
      sloAttributes.getQueryString().ifPresent(builder::queryString);
      sloAttributes.getRelayState().ifPresent(relayState -> builder.parameter("RelayState", relayState.value()));
      sloAttributes.getSignatureAlgorithm().ifPresent(alg -> builder.parameter("SigAlg", alg.value()));
      sloAttributes.getSignature().ifPresent(signature -> builder.parameter("Signature", signature.value()));
      return builder.build();
   }
}
