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

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.saml2.application.service.Saml2LogoutResumeRedirectService;
import com.airlock.iam.saml2.application.service.idp.Saml2SloService;
import com.airlock.iam.saml2.application.service.idp.Saml2SpInitiatedStartSloService;
import com.airlock.iam.saml2.domain.model.Saml2RelayState;
import com.airlock.iam.saml2.domain.model.idp.Saml2RequestParameter;
import com.airlock.iam.saml2.domain.model.idp.Saml2ResponseParameter;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.domain.service.Saml2LibraryIdpCacheService;
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 java.net.URI;
import java.util.Optional;
import org.apache.commons.lang3.Validate;

public final class Saml2SloServiceImpl implements Saml2SloService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2SloServiceImpl.class);
   private final Provider<Saml2Request> request;
   private final Provider<Saml2LogoutResumeRedirectService> redirectService;
   private final Provider<FlowFederationSessionRepository> sessionRepository;
   private final Provider<Saml2IdpFederationLibrary> library;
   private final Provider<Saml2LibraryIdpCacheService> libraryCache;
   private final Provider<Saml2SpInitiatedStartSloService> spInitiatedSloService;

   @Inject
   Saml2SloServiceImpl(
      Provider<Saml2Request> request,
      Provider<Saml2LogoutResumeRedirectService> redirectService,
      Provider<FlowFederationSessionRepository> sessionRepository,
      Provider<Saml2IdpFederationLibrary> library,
      Provider<Saml2LibraryIdpCacheService> libraryCache,
      Provider<Saml2SpInitiatedStartSloService> spInitiatedSloService
   ) {
      this.request = request;
      this.redirectService = redirectService;
      this.sessionRepository = sessionRepository;
      this.library = library;
      this.libraryCache = libraryCache;
      this.spInitiatedSloService = spInitiatedSloService;
   }

   @Override
   public Optional<URI> processSlo() {
      Optional<Saml2ResponseParameter> samlResponse = ((Saml2Request)this.request.get()).samlResponse();
      Optional<Saml2RequestParameter> samlRequest = ((Saml2Request)this.request.get()).samlRequest();
      Validate.isTrue(
         samlResponse.isPresent() ^ samlRequest.isPresent(),
         "Expected request to contain either a SAMLResponse parameter or a SAMLRequest parameter",
         new Object[0]
      );
      if (samlResponse.isPresent()) {
         this.ensureHasFederationSessionWithSloInProgress();
         Optional<Saml2RelayState> relayState = ((Saml2Request)this.request.get()).relayStateParameter();
         return this.processSamlResponseFromSp(samlResponse.get(), relayState);
      } else {
         return this.spInitiatedSlo();
      }
   }

   private void ensureHasFederationSessionWithSloInProgress() {
      Optional<FlowFederationSession> session = ((FlowFederationSessionRepository)this.sessionRepository.get()).getFederationSession();
      session.ifPresentOrElse(
         s -> Validate.isTrue(s.isSingleLogoutInProgress()),
         () -> ((FlowFederationSessionRepository)this.sessionRepository.get()).createNewFederationSession().setIdpInitiatedSingleLogoutInProgressOnIdp()
      );
   }

   private Optional<URI> processSamlResponseFromSp(Saml2ResponseParameter samlResponse, Optional<Saml2RelayState> relayState) {
      LOG.info("SAML2 SLO: Received SLO response from SP using " + this.validateBinding() + " binding.");
      if (relayState.isPresent()) {
         relayState = ((Saml2LibraryIdpCacheService)this.libraryCache.get()).resolveAndRemoveRelayStateFromCache(relayState.get());
      }

      boolean clientRedirected = ((Saml2IdpFederationLibrary)this.library.get()).processSingleLogoutResponse(samlResponse, relayState);
      if (!clientRedirected) {
         LOG.info("SAML2 SLO Succeeded");
         return Optional.of(((Saml2LogoutResumeRedirectService)this.redirectService.get()).cleanupSessionsAndRedirectToUiLogoutResumeUri());
      } else {
         return this.finishLogoutIfSloComplete();
      }
   }

   private Optional<URI> spInitiatedSlo() {
      LOG.info("SAML2 SLO: Starting SP-initiated SLO using " + this.validateBinding() + " binding.");
      return Optional.of(((Saml2SpInitiatedStartSloService)this.spInitiatedSloService.get()).startSpInitiatedSlo());
   }

   private String validateBinding() {
      String httpMethod = ((Saml2Request)this.request.get()).getMethod();

      return switch (httpMethod) {
         case "POST" -> "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
         case "GET" -> "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
         default -> throw new ViolationException("Unsupported HTTP method. Expected GET or POST but got " + LogUtil.logSafeQuote(httpMethod));
      };
   }

   private Optional<URI> finishLogoutIfSloComplete() {
      Optional<FlowFederationSession> session = ((FlowFederationSessionRepository)this.sessionRepository.get()).getFederationSession();
      if (session.isEmpty() || !session.get().isValid()) {
         ((Saml2LogoutResumeRedirectService)this.redirectService.get()).cleanupSessions();
      }

      return Optional.empty();
   }
}
