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

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.saml2.application.configuration.Saml2FlowIdpConfig;
import com.airlock.iam.saml2.application.service.idp.Saml2SpInitiatedSsoService;
import com.airlock.iam.saml2.domain.model.idp.Saml2FederationSessionIndex;
import com.airlock.iam.saml2.domain.model.logging.Saml2LogTypes;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.infrastructure.Saml2CommonSpaUrls;
import com.airlock.iam.saml2.infrastructure.service.Saml2SessionProvider;
import com.sun.identity.saml2.profile.IDPCache;
import com.sun.identity.saml2.protocol.AuthnRequest;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.net.URI;
import java.util.Optional;

public class Saml2SpInitiatedSsoServiceImpl implements Saml2SpInitiatedSsoService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2SpInitiatedSsoServiceImpl.class);
   private final Provider<FlowFederationSessionRepository> federationSessionRepository;
   private final Provider<Saml2IdpFederationLibrary> library;
   private final Provider<Saml2FlowIdpConfig> idpFlowConfig;
   private final Provider<Saml2SessionTerminationService> sessionTerminationService;
   private final Provider<Saml2AuthnRequestExtractor> authnRequestExtractor;
   private final Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory;
   private final Provider<Saml2SessionProvider> sessionProvider;

   @Inject
   public Saml2SpInitiatedSsoServiceImpl(
      Provider<FlowFederationSessionRepository> federationSessionRepository,
      Provider<Saml2IdpFederationLibrary> library,
      Provider<Saml2FlowIdpConfig> idpFlowConfig,
      Provider<Saml2SessionTerminationService> sessionTerminationService,
      Provider<Saml2AuthnRequestExtractor> authnRequestExtractor,
      Provider<AbsoluteExternalViewUriFactory> absoluteExternalViewUriFactory,
      Provider<Saml2SessionProvider> sessionProvider
   ) {
      this.federationSessionRepository = federationSessionRepository;
      this.library = library;
      this.idpFlowConfig = idpFlowConfig;
      this.sessionTerminationService = sessionTerminationService;
      this.authnRequestExtractor = authnRequestExtractor;
      this.absoluteExternalViewUriFactory = absoluteExternalViewUriFactory;
      this.sessionProvider = sessionProvider;
   }

   @Override
   public Optional<URI> handleSpInitiatedSso() {
      LOG.info("Received SSO request from SP using Redirect or POST binding.");
      Optional<FlowFederationSession> federationSession = ((FlowFederationSessionRepository)this.federationSessionRepository.get()).getFederationSession();
      if (federationSession.isPresent() && federationSession.get().isSingleLogoutInProgress()) {
         LOG.warn(
            Saml2LogTypes.FEDERATION_LIBRARY_MESSAGE,
            "Request received to SAML2 SSO but a SLO (single logout) has been started on this session before. This looks like a SLO process has been interrupted (e.g. a SAML SP failed to redirect the client back to us) the SAML IdP."
         );
         ((Saml2SessionProvider)this.sessionProvider.get()).invalidateSession(federationSession.get());
         return Optional.of(
            ((AbsoluteExternalViewUriFactory)this.absoluteExternalViewUriFactory.get())
               .createAbsoluteUri(((Saml2FlowIdpConfig)this.idpFlowConfig.get()).getCustomLogoutUri().orElse(Saml2CommonSpaUrls.UI_LOGOUT_URI.uri()))
         );
      }

      AuthnRequest authnReq = ((Saml2AuthnRequestExtractor)this.authnRequestExtractor.get())
         .extractAuthnRequestFromCurrentRequest()
         .orElseThrow(() -> new ViolationException("No valid SAMLRequest parameter provided"));
      Optional<Saml2FederationSessionIndex> sessionIndex = federationSession.flatMap(FlowFederationSession::getSessionIndex);
      if (sessionIndex.isPresent() && !IDPCache.idpSessionsByIndices().contains(sessionIndex.get().value())) {
         LOG.info(Saml2LogTypes.FEDERATION_LIBRARY_MESSAGE, "Invalidating unknown FlowFederationSession (not in the cache)");
         ((Saml2SessionProvider)this.sessionProvider.get()).invalidateSession(federationSession.get());
      }

      if (Boolean.TRUE.equals(authnReq.isForceAuthn())) {
         LOG.info(Saml2LogTypes.FEDERATION_LIBRARY_MESSAGE, "AuthnRequest forces a new authentication using the ForceAuthn attribute.");
         if (federationSession.isPresent()) {
            ((Saml2SessionProvider)this.sessionProvider.get()).invalidateSession(federationSession.get());
         }

         ((Saml2SessionTerminationService)this.sessionTerminationService.get()).terminateSessionsUsingClear();
      }

      ((Saml2IdpFederationLibrary)this.library.get()).doSpInitiatedSsoFederate();
      LOG.info("SSO request finished.");
      return Optional.empty();
   }
}
