package com.airlock.iam.authentication.infrastructure.factory;

import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.flow.api.domain.model.redflag.RedFlag;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.infrastructure.step.StepSessionModuleHelper;
import com.airlock.iam.log.audit.AuditLogger;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.provider.scope.OAuth2ScopeCondition;
import com.airlock.iam.oauth2.application.configuration.OAuth2AsConfigsSpec;
import com.airlock.iam.oauth2.application.configuration.OAuth2AuthorizationServerConfigSpec;
import com.airlock.iam.oauth2.application.configuration.OAuth2ConsentConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2ConsentStepConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2LocalConsentConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2RemoteConsentConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2ScopesSettingsConfig;
import com.airlock.iam.oauth2.application.service.OAuth2RemoteConsentServiceImpl;
import com.airlock.iam.oauth2.application.service.consent.OAuth2ConsentSettingsFactory;
import com.airlock.iam.oauth2.domain.model.client.OAuth2ClientLoader;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2AuthorizationCodeGrantFlowSessionAttribute;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2ScopesSettings;
import com.airlock.iam.oauth2.domain.model.consent.OAuth2LocalConsentSettings;
import com.airlock.iam.oauth2.domain.model.consent.OAuth2RemoteConsentSettings;
import com.airlock.iam.oauth2.domain.model.provider.OAuth2ConsentClientProvider;
import com.airlock.iam.oauth2.domain.model.provider.OAuth2ConsentClientProviderImpl;
import com.airlock.iam.oauth2.domain.model.provider.OAuth2ConsentStepSession;
import com.airlock.iam.oauth2.domain.model.remoteconsent.RemoteConsentEncoder;
import com.airlock.iam.oauth2.domain.model.remoteconsent.RemoteConsentVerifier;
import com.airlock.iam.oauth2.domain.model.settings.OAuth2OidcAuthorizationCodeGrantSettings;
import com.airlock.iam.oauth2.domain.service.OpenIdProviderUserAgentStateService;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2ConsentService;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2ConsentServiceImpl;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2LocalConsentStorageService;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2RemoteConsentService;
import com.airlock.iam.oauth2.domain.service.consent.OpenIdProviderUserAgentStateServiceImpl;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@ModuleForConfig(OAuth2ConsentStepConfig.class)
public class OAuth2ConsentStepModule extends PrivateModule {
   @Exposed
   @Provides
   OAuth2ConsentStepSession provideOAuth2ConsentStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.retrieveStepSession(flowSession, OAuth2ConsentStepSession.class);
   }

   @Exposed
   @Provides
   OAuth2ConsentClientProvider provideClientLoader(Optional<OAuth2AsConfigsSpec> config, OAuth2AuthorizationCodeGrantFlowSessionAttribute sessionAttribute) {
      OAuth2ClientLoader clientLoader = getOAuth2AuthorizationServerConfigSpec(config, sessionAttribute).getClientLoader();
      return new OAuth2ConsentClientProviderImpl(sessionAttribute, clientLoader);
   }

   @Provides
   Optional<OAuth2ScopesSettingsConfig> provideOAuth2ScopesSettingsConfig(
      Optional<OAuth2AsConfigsSpec> config, OAuth2AuthorizationCodeGrantFlowSessionAttribute sessionAttribute
   ) {
      return getOAuth2AuthorizationServerConfigSpec(config, sessionAttribute).getScopesSettingsConfig();
   }

   @Provides
   List<OAuth2ScopeCondition> provideOAuth2ScopeConditions(Optional<OAuth2ScopesSettingsConfig> scopesSettingsConfig, PluginConfigToLogic toLogic) {
      return scopesSettingsConfig.map(toLogic::getLogic).map(OAuth2ScopesSettings::scopeConditions).orElse(Collections.emptyList());
   }

   @Exposed
   @Provides
   RedFlag provideRedFlag(OAuth2ConsentStepConfig config) {
      return config.getRedFlag().toRedFlag();
   }

   @Provides
   OAuth2RemoteConsentSettings provideRemoteConsentSettings(Optional<OAuth2ConsentConfig> consentConfig) {
      OAuth2ConsentConfig config = ServiceConfigurationException.checkPresent(consentConfig, "An OAuth 2.0 Remote Consent Config is required.");
      ServiceConfigurationException.checkTrue(config instanceof OAuth2RemoteConsentConfig, "An OAuth 2.0 Remote Consent Config is required.");
      return (OAuth2RemoteConsentSettings)OAuth2ConsentSettingsFactory.consentSettings(consentConfig).orElseThrow();
   }

   @Provides
   OAuth2LocalConsentConfig provideLocalConsentConfig(Optional<OAuth2ConsentConfig> consentConfig) {
      OAuth2ConsentConfig config = ServiceConfigurationException.checkPresent(consentConfig, "An OAuth 2.0 Local Consent Config is required.");
      ServiceConfigurationException.checkTrue(config instanceof OAuth2LocalConsentConfig, "An OAuth 2.0 Local Consent Config is required.");
      return (OAuth2LocalConsentConfig)consentConfig.orElseThrow();
   }

   @Provides
   OAuth2LocalConsentSettings provideLocalConsentSettings(OAuth2LocalConsentConfig consentConfig) {
      return (OAuth2LocalConsentSettings)OAuth2ConsentSettingsFactory.consentSettings(consentConfig);
   }

   @Provides
   @Exposed
   Optional<OAuth2LocalConsentStorageService> provideLocalConsentStorageService(
      OAuth2AuthorizationCodeGrantFlowSessionAttribute sessionAttribute, Optional<OAuth2AsConfigsSpec> config, PluginConfigToLogic toLogic
   ) {
      return getOAuth2AuthorizationServerConfigSpec(config, sessionAttribute).getConsentStorageConfig().map(toLogic::getLogic);
   }

   @Provides
   RemoteConsentEncoder provideConsentEncoder(OAuth2RemoteConsentSettings settings) {
      return new RemoteConsentEncoder(settings.signer(), settings.encrypter(), AuditLogger::log);
   }

   @Provides
   RemoteConsentVerifier provideConsentVerifier(OAuth2RemoteConsentSettings settings, OAuth2OidcAuthorizationCodeGrantSettings asSettings) {
      return new RemoteConsentVerifier(settings.verifier(), settings.decrypter(), asSettings);
   }

   protected void configure() {
      this.bind(OAuth2ConsentService.class).to(OAuth2ConsentServiceImpl.class);
      this.expose(OAuth2ConsentService.class);
      this.bind(OAuth2RemoteConsentService.class).to(OAuth2RemoteConsentServiceImpl.class);
      this.expose(OAuth2RemoteConsentService.class);
      this.bind(OpenIdProviderUserAgentStateService.class).to(OpenIdProviderUserAgentStateServiceImpl.class);
      this.expose(OpenIdProviderUserAgentStateService.class);
   }

   private static OAuth2AuthorizationServerConfigSpec getOAuth2AuthorizationServerConfigSpec(
      Optional<OAuth2AsConfigsSpec> config, OAuth2AuthorizationCodeGrantFlowSessionAttribute oauth2AuthorizationCodeGrantFlowSessionAttribute
   ) {
      AuthorizationServerId authorizationServerId = oauth2AuthorizationCodeGrantFlowSessionAttribute.authorizationServerId();
      return ServiceConfigurationException.checkPresent(
         config.flatMap(configSpec -> configSpec.findOAuth2AuthorizationServerConfigSpec(authorizationServerId)),
         "Configuration for OAuth 2.0 authorization server with ID " + LogUtil.logSafeQuote(authorizationServerId.id()) + " is required"
      );
   }
}
