package com.airlock.iam.saml2.domain.service.logout;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.logoutaction.LogoutAction;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionUsage;
import com.airlock.iam.saml2.domain.service.Saml2FlowFederationSessionTerminationService;
import com.airlock.iam.saml2.domain.service.idp.logout.Saml2LogoutActionOnIdpService;
import com.airlock.iam.saml2.domain.service.sp.logout.Saml2LogoutActionOnSpService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import java.util.function.Consumer;
import lombok.NonNull;

public final class Saml2LogoutAction implements LogoutAction {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2LogoutAction.class);
   private final Provider<FlowFederationSessionRepository> federationSessionRepository;
   private final Provider<FlowFederationSessionUsage> federationSessionUsage;
   private final Provider<Saml2LogoutActionOnIdpService> logoutActionServiceOnIdp;
   private final Provider<Saml2LogoutActionOnSpService> logoutActionServiceOnSp;
   private final Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService;

   @Inject
   Saml2LogoutAction(
      @NonNull Provider<FlowFederationSessionRepository> federationSessionRepository,
      @NonNull Provider<FlowFederationSessionUsage> federationSessionUsage,
      @NonNull Provider<Saml2LogoutActionOnIdpService> logoutActionServiceOnIdp,
      @NonNull Provider<Saml2LogoutActionOnSpService> logoutActionServiceOnSp,
      @NonNull Provider<Saml2FlowFederationSessionTerminationService> federationSessionTerminationService
   ) {
      if (federationSessionRepository == null) {
         throw new NullPointerException("federationSessionRepository is marked non-null but is null");
      }

      if (federationSessionUsage == null) {
         throw new NullPointerException("federationSessionUsage is marked non-null but is null");
      }

      if (logoutActionServiceOnIdp == null) {
         throw new NullPointerException("logoutActionServiceOnIdp is marked non-null but is null");
      }

      if (logoutActionServiceOnSp == null) {
         throw new NullPointerException("logoutActionServiceOnSp is marked non-null but is null");
      }

      if (federationSessionTerminationService == null) {
         throw new NullPointerException("federationSessionTerminationService is marked non-null but is null");
      }

      this.federationSessionRepository = federationSessionRepository;
      this.federationSessionUsage = federationSessionUsage;
      this.logoutActionServiceOnIdp = logoutActionServiceOnIdp;
      this.logoutActionServiceOnSp = logoutActionServiceOnSp;
      this.federationSessionTerminationService = federationSessionTerminationService;
   }

   @Override
   public boolean isApplicable() {
      return ((FlowFederationSessionRepository)this.federationSessionRepository.get()).getFederationSession().isPresent();
   }

   @Override
   public void execute() {
      FlowFederationSession federationSession = ((FlowFederationSessionRepository)this.federationSessionRepository.get())
         .getFederationSession()
         .orElseThrow(() -> new FailureException("Mandatory FlowFederationSession absent"));
      Optional<FlowFederationSessionUsage.FlowFederationSessionType> federationSessionType = ((FlowFederationSessionUsage)this.federationSessionUsage.get())
         .usageOf(federationSession);
      if (federationSessionType.isEmpty()) {
         LOG.info(
            String.format(
               "SAML 2.0 Single Logout: Terminating unused federation session for %s.",
               federationSession.getAuthenticatedUserId().map(id -> String.format("user %s", LogUtil.logSafeQuote(id))).orElse("unknown user")
            )
         );
         ((Saml2FlowFederationSessionTerminationService)this.federationSessionTerminationService.get()).terminateSession();
      } else {
         Consumer<FlowFederationSession> sloActionPerformer = switch ((FlowFederationSessionUsage.FlowFederationSessionType)federationSessionType.get()) {
            case SP_ONLY -> ((Saml2LogoutActionOnSpService)this.logoutActionServiceOnSp.get())::performSloAction;
            case IDP_ONLY -> ((Saml2LogoutActionOnIdpService)this.logoutActionServiceOnIdp.get())::performSloAction;
            case IDP_AND_SP -> {
               if (federationSession.isSingleLogoutInProgressOnSp()) {
                  LOG.info(
                     "SAML 2.0 SLO on SP: Continuing Single Logout (SLO) for IAM as SP on federation session which is used for both IAM as SP and IAM as IdP."
                  );
                  yield ((Saml2LogoutActionOnSpService)this.logoutActionServiceOnSp.get())::performSloAction;
               } else {
                  yield ((Saml2LogoutActionOnIdpService)this.logoutActionServiceOnIdp.get())::performSloAction;
               }
            }
         };
         sloActionPerformer.accept(federationSession);
      }
   }
}
