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.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.domain.model.idp.logout.Saml2IdpInitiatedSloId;
import com.airlock.iam.saml2.domain.model.logout.Saml2ClearGatewaySessionOnSloSetting;
import com.airlock.iam.saml2.domain.model.logout.Saml2SloRedirectLogoutPropagationAttribute;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.service.Saml2FlowFederationSessionTerminationService;
import com.airlock.iam.saml2.domain.service.Saml2SloCredentialService;
import com.airlock.iam.saml2.domain.service.idp.logout.Saml2LogoutActionOnIdpService;
import com.airlock.iam.saml2.infrastructure.idp.Saml2IdpJerseyUrls;
import com.airlock.iam.saml2.infrastructure.idp.logout.Saml2IdpInitiatedSloFederationSessionCache;
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 Saml2LogoutActionOnIdpServiceImpl implements Saml2LogoutActionOnIdpService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2LogoutActionOnIdpServiceImpl.class);
   private static final String IDP_INITIATED_SLO_ID_PARAMETER_NAME = "id";
   private final Provider<UserSession> userSession;
   private final Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory;
   private final Provider<Saml2SloCredentialService> sloCredentialService;
   private final Provider<Saml2IdpInitiatedSloFederationSessionCache> idpInitiatedSloCache;
   private final Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService;
   private final Provider<Saml2ClearGatewaySessionOnSloSetting> clearGatewaySessionOnSloSetting;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   Saml2LogoutActionOnIdpServiceImpl(
      Provider<UserSession> userSession,
      Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory,
      Provider<Saml2SloCredentialService> sloCredentialService,
      Provider<Saml2IdpInitiatedSloFederationSessionCache> idpInitiatedSloCache,
      Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService,
      Provider<Saml2ClearGatewaySessionOnSloSetting> clearGatewaySessionOnSloSetting,
      SigningLogger signingLogger,
      HelpdeskLogger helpdeskLogger
   ) {
      this.userSession = userSession;
      this.absoluteExternalViewUriFactory = absoluteExternalViewUriFactory;
      this.sloCredentialService = sloCredentialService;
      this.idpInitiatedSloCache = idpInitiatedSloCache;
      this.federationSessionTerminationService = federationSessionTerminationService;
      this.clearGatewaySessionOnSloSetting = clearGatewaySessionOnSloSetting;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

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

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

   private void spInitiatedSloAction(FlowFederationSession federationSession) {
      this.setTemporaryLogoutCredentials(federationSession);
      this.logSloInit("SP", federationSession.getAuthenticatedUserId());
   }

   private void idpInitiatedSloAction(FlowFederationSession federationSession) {
      federationSession.setIdpInitiatedSingleLogoutInProgressOnIdp();
      Saml2IdpInitiatedSloId sloId = ((Saml2IdpInitiatedSloFederationSessionCache)this.idpInitiatedSloCache.get()).save(federationSession);
      this.addSloRedirectLogoutPropagationAttribute(sloId);
      this.setTemporaryLogoutCredentials(federationSession);
      this.logSloInit("IdP", federationSession.getAuthenticatedUserId());
   }

   private void setTemporaryLogoutCredentials(FlowFederationSession federationSession) {
      if (((Saml2ClearGatewaySessionOnSloSetting)this.clearGatewaySessionOnSloSetting.get()).isClearSession()) {
         ((Saml2SloCredentialService)this.sloCredentialService.get()).addTemporaryLogoutCredential();
      } else {
         federationSession.doNotClearGatewaySessionOnSlo();
         ((Saml2SloCredentialService)this.sloCredentialService.get()).setTemporaryLogoutCredential();
      }
   }

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

   private URI createLogoutPropagationUri(Saml2IdpInitiatedSloId sloId) {
      return ((AbsoluteExternalViewUriFactory)this.absoluteExternalViewUriFactory.get())
         .createAbsoluteUri(UriBuilder.fromUri(Saml2IdpJerseyUrls.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 IdP: 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 IdP: 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);
   }
}
