package com.airlock.iam.authentication.application.service.saml2.idp;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationFlowStep;
import com.airlock.iam.authentication.application.service.ApplicationAccessService;
import com.airlock.iam.authentication.application.service.InternalApplicationAccessService;
import com.airlock.iam.authentication.application.service.targetapp.TargetApplicationRepository;
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.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.domain.model.result.FlowResultImpl;
import com.airlock.iam.flow.impl.domain.model.step.DefaultStepResultFactory;
import com.airlock.iam.login.application.configuration.targetapp.ApplicationIdConfig;
import com.airlock.iam.saml2.application.configuration.Saml2ServiceProviderAccessConfig;
import com.airlock.iam.saml2.domain.model.idp.Saml2RequestId;
import com.airlock.iam.saml2.domain.model.idp.Saml2SpInitiatedRequestAttribute;
import com.airlock.iam.saml2.domain.model.logging.Saml2LogTypes;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpEntityId;
import com.airlock.iam.saml2.domain.service.Saml2IdpFlowConfigService;
import com.airlock.iam.saml2.domain.service.Saml2LibraryIdpCacheService;
import com.sun.identity.saml2.protocol.AuthnRequest;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

public final class Saml2SpInitiatedSsoAccessService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2SpInitiatedSsoAccessService.class);
   private final Provider<ApplicationAccessService> applicationAccessService;
   private final Provider<Saml2LibraryIdpCacheService> saml2LibraryIdpCacheService;
   private final Provider<Saml2IdpFlowConfigService> saml2IdpFlowConfigService;

   @Inject
   public Saml2SpInitiatedSsoAccessService(
      Provider<ApplicationAccessService> applicationAccessService,
      Provider<TargetApplicationRepository> applicationRepository,
      Provider<Saml2LibraryIdpCacheService> saml2LibraryIdpCacheService,
      Provider<Saml2IdpFlowConfigService> saml2IdpFlowConfigService
   ) {
      this.applicationAccessService = applicationAccessService;
      this.saml2LibraryIdpCacheService = saml2LibraryIdpCacheService;
      this.saml2IdpFlowConfigService = saml2IdpFlowConfigService;
   }

   public FlowResult startNewFlowBySamlRequestId(Saml2RequestId requestId) {
      Optional<AuthnRequest> authnRequest = ((Saml2LibraryIdpCacheService)this.saml2LibraryIdpCacheService.get()).getAuthnRequestByRequestId(requestId);
      if (authnRequest.isEmpty()) {
         LOG.info(Saml2LogTypes.FEDERATION_LIBRARY_MESSAGE, "Request ID " + LogUtil.logSafeQuote(requestId) + " does not reference a previous SAML Request.");
         return FlowResultImpl.of(new DefaultStepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build());
      } else {
         Saml2SpEntityId issuerId = Saml2SpEntityId.spEntityId(authnRequest.get().getIssuer().getValue());
         Optional<Saml2ServiceProviderAccessConfig> spAccessConfig = ((Saml2IdpFlowConfigService)this.saml2IdpFlowConfigService.get())
            .getSpAccessConfigBySpIssuer(issuerId);
         if (spAccessConfig.isEmpty()) {
            LOG.error(Saml2LogTypes.FEDERATION_LIBRARY_MESSAGE, "SP associated with request ID " + LogUtil.logSafeQuote(requestId) + " is not configured.");
            return FlowResultImpl.of(new DefaultStepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build());
         } else {
            Optional<ApplicationIdConfig> requestedAuthnContextAppIdConfig = ((Saml2IdpFlowConfigService)this.saml2IdpFlowConfigService.get())
               .getRequestedAuthnContextAppIdConfig(authnRequest.get(), spAccessConfig.get());
            if (requestedAuthnContextAppIdConfig.isPresent()) {
               LOG.info(
                  "Application Access for ID "
                     + LogUtil.logSafeQuote(requestedAuthnContextAppIdConfig.get().getId())
                     + " for SAML 2.0 SP entity ID "
                     + LogUtil.logSafeQuote(issuerId.value())
                     + " due to matching requested AuthnContextClassRef."
               );
               return ((ApplicationAccessService)this.applicationAccessService.get())
                  .startNewFlowForApplication(requestedAuthnContextAppIdConfig.get().getId(), storeRequestIdAndIssuerId(requestId, issuerId));
            } else {
               Optional<ApplicationIdConfig> appIdConfig = Optional.ofNullable(spAccessConfig.get().getDefaultFlowApplicationId());
               if (appIdConfig.isEmpty()) {
                  LOG.info(
                     "Application Access for default application for SAML 2.0 SP entity ID "
                        + LogUtil.logSafeQuote(issuerId.value())
                        + " due to no SP-specific flow configured."
                  );
                  return ((ApplicationAccessService)this.applicationAccessService.get())
                     .startNewFlowForDefaultApplication(storeRequestIdAndIssuerId(requestId, issuerId));
               } else {
                  LOG.info(
                     "Starting flow "
                        + LogUtil.logSafeQuote(appIdConfig.get().getId())
                        + " for SAML 2.0 SP entity ID "
                        + LogUtil.logSafeQuote(issuerId.value())
                        + " due to SP-specific flow configured."
                  );
                  return ((ApplicationAccessService)this.applicationAccessService.get())
                     .startNewFlowForApplication(appIdConfig.get().getId(), storeRequestIdAndIssuerId(requestId, issuerId));
               }
            }
         }
      }
   }

   private static InternalApplicationAccessService.FlowSessionUpdater storeRequestIdAndIssuerId(Saml2RequestId requestId, Saml2SpEntityId issuer) {
      return flowSession -> flowSession.flowAttributes().putOrMergeAttribute(new Saml2SpInitiatedRequestAttribute(requestId, issuer));
   }

   @SuppressFBWarnings(justification = "generated code")
   public static Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder builder() {
      return new Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2SpInitiatedSsoAccessServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<ApplicationAccessService> applicationAccessService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<TargetApplicationRepository> applicationRepository;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2LibraryIdpCacheService> saml2LibraryIdpCacheService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2IdpFlowConfigService> saml2IdpFlowConfigService;

      @SuppressFBWarnings(justification = "generated code")
      Saml2SpInitiatedSsoAccessServiceBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder applicationAccessService(
         Provider<ApplicationAccessService> applicationAccessService
      ) {
         this.applicationAccessService = applicationAccessService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder applicationRepository(
         Provider<TargetApplicationRepository> applicationRepository
      ) {
         this.applicationRepository = applicationRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder saml2LibraryIdpCacheService(
         Provider<Saml2LibraryIdpCacheService> saml2LibraryIdpCacheService
      ) {
         this.saml2LibraryIdpCacheService = saml2LibraryIdpCacheService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder saml2IdpFlowConfigService(
         Provider<Saml2IdpFlowConfigService> saml2IdpFlowConfigService
      ) {
         this.saml2IdpFlowConfigService = saml2IdpFlowConfigService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpInitiatedSsoAccessService build() {
         return new Saml2SpInitiatedSsoAccessService(
            this.applicationAccessService, this.applicationRepository, this.saml2LibraryIdpCacheService, this.saml2IdpFlowConfigService
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2SpInitiatedSsoAccessService.Saml2SpInitiatedSsoAccessServiceBuilder(applicationAccessService="
            + this.applicationAccessService
            + ", applicationRepository="
            + this.applicationRepository
            + ", saml2LibraryIdpCacheService="
            + this.saml2LibraryIdpCacheService
            + ", saml2IdpFlowConfigService="
            + this.saml2IdpFlowConfigService
            + ")";
      }
   }
}
