package com.airlock.iam.oauth2.application.configuration;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.domain.model.provider.ProviderId;
import com.airlock.iam.core.domain.model.http.client.HttpClient;
import com.airlock.iam.core.misc.util.httpclient.HttpClientConfig;
import com.airlock.iam.flow.api.application.configuration.redflag.RedFlagConfig;
import com.airlock.iam.oauth2.application.configuration.accountlinking.AccountLinkingSelfServiceConfig;
import com.airlock.iam.oauth2.application.configuration.accountregistration.AccountRegistrationConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2ClientResourceRequest;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2RedirectUriConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2RequestBuilderConfig;
import com.airlock.iam.oauth2.application.configuration.logging.OAuth2LoggingSettings;
import com.airlock.iam.oauth2.domain.model.client.communication.OAuth2TokenRequestClientSecretProcessor;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.license.PluginPropertyLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Optional;

public abstract class OAuth2SsoFlowClientSettingsBase extends OAuth2ClientSettingsBase {
   @PluginProperty(
      displayName = "Provider Identifier",
      description = "An identifier to identify the OAuth 2.0 Authorization Server or OpenID Provider.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private OAuth2ProviderIdentifierConfig providerId;
   public static final String PROVIDER_ID_PROPERTY_NAME = "providerId";
   public static final String PROVIDER_IDENTIFIER_DISPLAY_NAME = "Provider Identifier";
   @PluginProperty(
      displayName = "Client Redirect URI",
      description = "<p>Defines the redirect URI (<tt>redirect_uri</tt>) parameter value to be included in OAuth 2.0 requests.\nThe authorization response will then be sent to this URI by the authorization server (AS) or OpenID Provider (OP).</p>\n<p>For redirects to the default IAM Loginapp UI use the \"OAuth 2.0 Default UI Client Redirect URI\".</p>\n",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Authorization Request")
   private OAuth2RedirectUriConfig clientRedirectURI;
   @PluginListProperty(
      description = "<p>Resource requests that will be executed to determine the identity of the user on the provider.</p>An OAuth 2.0 credential containing data of these resources is instantiated. This credential can then be used by plugins such as OAuth 2.0 Credential Roles Provider and OAuth 2.0 Credential Context Data Map to provide the data from the Authorization Server to the ID Propagation. This enables the ability to propagate the data to the backends.",
      displayName = "Resource Requests",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Resource Mappings")
   private List<OAuth2ClientResourceRequest> resourceRequests;
   public static final String RESOURCE_REQUESTS_FIELD = "resourceRequests";
   public static final String RESOURCE_REQUESTS_DISPLAY_NAME = "Resource Requests";
   @PluginProperty(
      description = "<p>If enabled, this provider is available in the account linking self-service.</p>Users can link their IAM account with this provider to have an alternative authentication method.<p>The account link management is available for authenticated users under the Loginapp URL: &lt;loginapp-uri&gt;/ui/app/protected/account-links</p>",
      displayName = "Account Linking Self-Service",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Account Linking and Registration")
   @PluginPropertyLicenseRestriction(moduleAffiliation = {"OAuthAccountLinking", "OAuthSocialRegistration"})
   private AccountLinkingSelfServiceConfig accountLinkingSelfService;
   static final String ACCOUNT_LINKING_SELF_SERVICE_FIELD = "accountLinkingSelfService";
   public static final String ACCOUNT_LINKING_SELF_SERVICE_DISPLAY_NAME = "Account Linking Self-Service";
   @PluginProperty(
      description = "<p>If configured, the flow will raise the configured red flag and continue in case no user could be identified using an account link.</p>This red flag can then be used by a following subflow to:<ol><li>be triggered (by using Account Linking Required Red Flag Condition as condition for the subflow)</li><li>identify the local user with authentication steps</li><li>link the identified user to the provider account and take down the red flag (by using Missing Account Link Step as step in the subflow)</li></ol> ",
      displayName = "Missing Account Link Red Flag",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Account Linking and Registration")
   @PluginPropertyLicenseRestriction(moduleAffiliation = {"OAuthAccountLinking", "OAuthSocialRegistration"})
   private RedFlagConfig missingAccountLinkRedFlag;
   public static final String MISSING_ACCOUNT_LINK_RED_FLAG_FIELD = "missingAccountLinkRedFlag";
   public static final String MISSING_ACCOUNT_LINK_RED_FLAG_DISPLAY_NAME = "Missing Account Link Red Flag";
   private HttpClient httpClientInstance;

   protected OAuth2SsoFlowClientSettingsBase(
      String clientId,
      String clientSecret,
      OAuth2RequestBuilderConfig accessTokenRequestMethod,
      OAuth2LoggingSettings loggingSettings,
      boolean enableAccountLinking,
      String autoLinkExistingUsersContextDataField,
      AccountRegistrationConfig accountRegistrationConfig,
      OAuth2ProviderIdentifierConfig providerId,
      OAuth2RedirectUriConfig clientRedirectURI,
      List<OAuth2ClientResourceRequest> resourceRequests,
      AccountLinkingSelfServiceConfig accountLinkingSelfService,
      RedFlagConfig missingAccountLinkRedFlag
   ) {
      super(
         clientId,
         clientSecret,
         accessTokenRequestMethod,
         loggingSettings,
         enableAccountLinking,
         autoLinkExistingUsersContextDataField,
         accountRegistrationConfig
      );
      this.providerId = providerId;
      this.clientRedirectURI = clientRedirectURI;
      this.resourceRequests = resourceRequests;
      this.accountLinkingSelfService = accountLinkingSelfService;
      this.missingAccountLinkRedFlag = missingAccountLinkRedFlag;
   }

   public abstract HttpClientConfig getHttpClient();

   public abstract OAuth2TokenRequestClientSecretProcessor getTokenEndpointAuthentication();

   @AfterConfigActivation
   void initializePlugin() {
      this.initializeHttpClient();
   }

   protected final void initializeHttpClient() {
      try {
         this.httpClientInstance = this.getHttpClient().createHttpClientForDomainClasses();
      } catch (GeneralSecurityException | IOException e) {
         throw new FailureException("Could not initialize http client", e);
      }
   }

   @ConfigurationValidator
   static void validateAccountLinkingDependencies(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      validateAccountLinkingRegistrationConfig(config, env, context);
      validateAccountLinkingSelfServiceConfig(config, env, context);
      validateLinkExistingSelfServiceConfig(config, env, context);
      validateUnlinkAccountConfig(config, env, context);
   }

   private static void validateUnlinkAccountConfig(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyConfigurationException {
      PluginConfiguration missingAccountLinkRedFlag = PluginPropertyValueReader.effectivePluginPropertyValue(config, "missingAccountLinkRedFlag", env, context);
      if (missingAccountLinkRedFlag != null) {
         if (PluginPropertyValueReader.effectivePluginPropertyValue(config, "accountRegistrationConfig", env, context) != null) {
            throw new PropertyConfigurationException(
               config,
               env,
               context,
               "'Missing Account Link Red Flag' cannot be enabled if 'Automated Account Registration' is configured.",
               new String[]{"missingAccountLinkRedFlag", "accountRegistrationConfig"}
            );
         }

         assureAccountLinkingEnabled("missingAccountLinkRedFlag", "Missing Account Link Red Flag", config, env, context);
      }
   }

   private static void validateAccountLinkingSelfServiceConfig(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyConfigurationException {
      PluginConfiguration acccountLinkingSelfService = PluginPropertyValueReader.effectivePluginPropertyValue(config, "accountLinkingSelfService", env, context);
      if (acccountLinkingSelfService != null) {
         assureAccountLinkingEnabled("accountLinkingSelfService", "Account Linking Self-Service", config, env, context);
      }
   }

   @Override
   public ProviderId getProviderId() {
      return this.providerId.providerId();
   }

   public Optional<AccountLinkingSelfServiceConfig> getAccountLinkingSelfService() {
      return Optional.ofNullable(this.accountLinkingSelfService);
   }

   public Optional<RedFlagConfig> getMissingAccountLinkRedFlag() {
      return Optional.ofNullable(this.missingAccountLinkRedFlag);
   }

   public abstract Optional<URI> getPushedAuthorizationRequestEndpointURL();

   public List<OAuth2ClientResourceRequest> getResourceRequests() {
      return this.resourceRequests;
   }

   public OAuth2RedirectUriConfig getClientRedirectURI() {
      return this.clientRedirectURI;
   }

   protected HttpClient getHttpClientInstance() {
      return this.httpClientInstance;
   }

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2SsoFlowClientSettingsBase() {
   }
}
