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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.infrastructure.factory.uri.AbsoluteExternalViewUriFactory;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.saml2.application.configuration.SamlConfig;
import com.airlock.iam.saml2.domain.model.logout.Saml2SloRedirectLogoutPropagationAttribute;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.sp.logout.Saml2SpInitiatedSloId;
import com.airlock.iam.saml2.domain.service.Saml2FlowFederationSessionTerminationService;
import com.airlock.iam.saml2.domain.service.sp.logout.Saml2LogoutActionOnSpService;
import com.airlock.iam.saml2.infrastructure.sp.Saml2SpJerseyUrls;
import com.airlock.iam.saml2.infrastructure.sp.logout.Saml2SpInitiatedSloFederationSessionCache;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.Optional;

public final class Saml2LogoutActionOnSpServiceImpl implements Saml2LogoutActionOnSpService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2LogoutActionOnSpServiceImpl.class);
   private static final String SP_INITIATED_SLO_ID_PARAMETER_NAME = "id";
   private final Provider<UserSession> userSession;
   private final Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory;
   private final Provider<Optional<SamlConfig>> samlConfig;
   private final Provider<Saml2SpInitiatedSloFederationSessionCache> spInitiatedSloCache;
   private final Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   Saml2LogoutActionOnSpServiceImpl(
      Provider<UserSession> userSession,
      Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory,
      Provider<Optional<SamlConfig>> samlConfig,
      Provider<Saml2SpInitiatedSloFederationSessionCache> spInitiatedSloCache,
      Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService,
      SigningLogger signingLogger,
      HelpdeskLogger helpdeskLogger
   ) {
      this.userSession = userSession;
      this.absoluteExternalViewUriFactory = absoluteExternalViewUriFactory;
      this.samlConfig = samlConfig;
      this.spInitiatedSloCache = spInitiatedSloCache;
      this.federationSessionTerminationService = federationSessionTerminationService;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   @Override
   public void performSloAction(FlowFederationSession federationSession) {
      if (!this.shouldPerformSloOnSp()) {
         this.localLogoutAction(federationSession);
      } else if (federationSession.isIdpInitiatedSingleLogoutInProgressOnSp()) {
         this.idpInitiatedSloAction(federationSession);
      } else if (federationSession.isSingleLogoutInProgress()) {
         this.logSloAlreadyInProgress(federationSession);
         this.localLogoutAction(federationSession);
      } else {
         this.spInitiatedSloAction(federationSession);
      }
   }

   private boolean shouldPerformSloOnSp() {
      return ((Optional)this.samlConfig.get()).map(SamlConfig::isSingleLogoutEnabledOnSps).orElse(false);
   }

   private void localLogoutAction(FlowFederationSession federationSession) {
      ((Saml2FlowFederationSessionTerminationService)this.federationSessionTerminationService.get()).terminateSession();
      this.logLocalLogout(federationSession.getAuthenticatedUserId());
   }

   private void idpInitiatedSloAction(FlowFederationSession federationSession) {
      this.logSloInit("IdP", federationSession.getAuthenticatedUserId());
   }

   private void spInitiatedSloAction(FlowFederationSession federationSession) {
      federationSession.setSpInitiatedSingleLogoutInProgressOnSp();
      Saml2SpInitiatedSloId sloId = ((Saml2SpInitiatedSloFederationSessionCache)this.spInitiatedSloCache.get()).save(federationSession);
      this.addSloRedirectLogoutPropagationAttribute(sloId);
      this.logSloInit("SP", federationSession.getAuthenticatedUserId());
   }

   private void addSloRedirectLogoutPropagationAttribute(Saml2SpInitiatedSloId sloId) {
      URI sloRedirectUri = this.createLogoutPropagationUri(sloId);
      ((UserSession)this.userSession.get()).addUserSessionAttribute(new Saml2SloRedirectLogoutPropagationAttribute(sloRedirectUri));
   }

   private URI createLogoutPropagationUri(Saml2SpInitiatedSloId sloId) {
      return ((AbsoluteExternalViewUriFactory)this.absoluteExternalViewUriFactory.get())
         .createAbsoluteUri(UriBuilder.fromUri(Saml2SpJerseyUrls.SLO_INIT_URI.path()).queryParam("id", new Object[]{sloId.value()}).build(new Object[0]));
   }

   private void logSloAlreadyInProgress(FlowFederationSession federationSession) {
      String sloType = switch (federationSession.getSingleLogoutInProgress()) {
         case SP_INITIATED_ON_SP -> "SP-initiated SLO on IAM as SP";
         case SP_INITIATED_ON_IDP -> "SP-initiated SLO on IAM as IdP";
         case IDP_INITIATED_ON_SP -> "IdP-initiated SLO on IAM as SP";
         case IDP_INITIATED_ON_IDP -> "IdP-initiated SLO on IAM as IdP";
         case NO_SLO_IN_PROGRESS -> throw new FailureException("Mandatory logout in progress absent");
      };
      LOG.info(String.format("SAML 2.0 SLO on SP: An %s is already in progress.", sloType));
   }

   private void logLocalLogout(Optional<UserId> userId) {
      String message = String.format(
         "Terminating federation session and performing local logout for %s.",
         userId.<String>map(id -> String.format("user %s", LogUtil.logSafeQuote(id))).orElse("unknown user")
      );
      LOG.debug(message);
   }

   private void logSloInit(String initiationType, Optional<UserId> userId) {
      String message = String.format(
         "SAML 2.0 %s-initiated SLO on SP: performing SLO for %s.",
         initiationType,
         userId.<String>map(id -> String.format("user %s", LogUtil.logSafeQuote(id))).orElse("unknown user")
      );
      this.signingLogger.log(message);
      this.helpdeskLogger.log(userId.orElse(null), message);
   }
}
