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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.CurrentLocale;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.domain.model.locale.LocalePreferences;
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.saml2.application.configuration.sp.Saml2FlowSpConfig;
import com.airlock.iam.saml2.application.service.sp.Saml2SpAssertionConsumerService;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpAssertionAttributes;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpSsoAssertionData;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpSsoContinuationId;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpSsoMetadata;
import com.airlock.iam.saml2.domain.model.sp.step.Saml2SpSsoSessionAttribute;
import com.airlock.iam.saml2.domain.service.Saml2FlowSpConfigService;
import com.airlock.iam.saml2.infrastructure.service.sp.Saml2SpFederationLibrary;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.Locale;
import java.util.Optional;

public final class Saml2SpAssertionConsumerServiceImpl implements Saml2SpAssertionConsumerService {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2SpAssertionConsumerServiceImpl.class);
   private static final String SSO_CONTINUATION_ID_QUERY_PARAMETER_NAME = "continuationId";
   private static final String LANGUAGE_PARAMETER_NAME = "lang";
   private final Provider<UserSession> userSession;
   private final Provider<AbsoluteExternalViewUriFactory> externalViewUriFactory;
   private final Provider<Saml2SpFederationLibrary> library;
   private final Provider<URI> uiSsoContinueUri;
   private final Provider<LocalePreferences> localePreferences;
   private final Provider<CurrentLocale> currentLocale;
   private final Provider<Saml2FlowSpConfigService> spConfigService;

   @Inject
   Saml2SpAssertionConsumerServiceImpl(
      Provider<UserSession> userSession,
      Provider<AbsoluteExternalViewUriFactory> externalViewUriFactory,
      Provider<Saml2SpFederationLibrary> library,
      Provider<URI> uiSsoContinueUri,
      Provider<LocalePreferences> localePreferences,
      Provider<CurrentLocale> currentLocale,
      Provider<Saml2FlowSpConfigService> spConfigService
   ) {
      this.userSession = userSession;
      this.externalViewUriFactory = externalViewUriFactory;
      this.library = library;
      this.uiSsoContinueUri = uiSsoContinueUri;
      this.localePreferences = localePreferences;
      this.currentLocale = currentLocale;
      this.spConfigService = spConfigService;
   }

   @Override
   public URI consumeAssertion() {
      LOG.info("Received SAML2 Assertion or Artifact...");
      AssertionData assertionData = ((Saml2SpFederationLibrary)this.library.get()).processAssertionFromSamlResponse();
      LOG.info("SAML2 Assertion processed.");
      ProvidedUsername providedUsernameFromNameID = assertionData.getProvidedUsernameFromNameID();
      Saml2FlowSpConfig saml2FlowSpConfig = ((Saml2FlowSpConfigService)this.spConfigService.get())
         .findFlowSpConfig(assertionData.getSpEntityId())
         .orElseThrow(
            () -> new FailureException("SP Entity ID %s from assertion is not configured.".formatted(LogUtil.logSafeQuote(assertionData.getSpEntityId())))
         );
      Optional<Locale> newLocale = this.resolveRequestedLocaleAndUpdateCurrentIfNecessary(assertionData, saml2FlowSpConfig);
      Saml2SpSsoAssertionData data = new Saml2SpSsoAssertionData(
         new Saml2SpSsoMetadata(assertionData.getInResponseTo(), assertionData.getRelayState(), assertionData.getSpEntityId()),
         new Saml2SpAssertionAttributes(assertionData.getSpEntityId(), providedUsernameFromNameID, assertionData.getAttributes())
      );
      Saml2SpSsoContinuationId continuationId = Saml2SpSsoContinuationId.newSsoContinuationId();
      ((UserSession)this.userSession.get()).addUserSessionAttribute(new Saml2SpSsoSessionAttribute(continuationId, data));
      return ((AbsoluteExternalViewUriFactory)this.externalViewUriFactory.get()).createAbsoluteUri(this.getContinuationUri(continuationId, newLocale));
   }

   @Nonnull
   private Optional<Locale> resolveRequestedLocaleAndUpdateCurrentIfNecessary(AssertionData assertionData, Saml2FlowSpConfig saml2FlowSpConfig) {
      Optional<String> configuredLanguageAttributeName = saml2FlowSpConfig.getAttributeToImportAsLanguage();
      Optional<String> requestedLanguage = configuredLanguageAttributeName.flatMap(
         languageAttributeName -> assertionData.getAttributes().getSingleValueAttribute(languageAttributeName)
      );
      if (requestedLanguage.isEmpty()) {
         if (configuredLanguageAttributeName.isPresent()) {
            LOG.info(
               "No attribute value present in assertion for configured language attribute %s."
                  .formatted(LogUtil.logSafeQuote(configuredLanguageAttributeName.get()))
            );
         }

         return Optional.empty();
      } else {
         Optional<Locale> resolvedRequestedLocale = ((LocalePreferences)this.localePreferences.get()).forLanguageWithoutDefault(requestedLanguage.get());
         if (resolvedRequestedLocale.isEmpty()) {
            LOG.info(
               "Requested language %s from assertion is not supported. Keeping %s."
                  .formatted(LogUtil.logSafeQuote(requestedLanguage.get()), LogUtil.logSafeQuote(((CurrentLocale)this.currentLocale.get()).get()))
            );
            return Optional.empty();
         } else if (resolvedRequestedLocale.get().equals(((CurrentLocale)this.currentLocale.get()).get())) {
            LOG.debug(
               "Requested language %s from assertion resolves to current locale %s."
                  .formatted(LogUtil.logSafeQuote(requestedLanguage.get()), LogUtil.logSafeQuote(((CurrentLocale)this.currentLocale.get()).get()))
            );
            return Optional.empty();
         } else {
            ((CurrentLocale)this.currentLocale.get()).update(resolvedRequestedLocale.get());
            return resolvedRequestedLocale;
         }
      }
   }

   private URI getContinuationUri(Saml2SpSsoContinuationId continuationId, Optional<Locale> locale) {
      UriBuilder uriBuilder = UriBuilder.fromUri((URI)this.uiSsoContinueUri.get()).queryParam("continuationId", new Object[]{continuationId.value()});
      locale.ifPresent(requestedLocale -> uriBuilder.queryParam("lang", new Object[]{requestedLocale.getLanguage()}));
      return uriBuilder.build(new Object[0]);
   }
}
