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

import com.airlock.iam.common.domain.model.provider.ProviderId;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.log.IntegrationLogger;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.application.configuration.accountregistration.AccountRegistrationConfig;
import com.airlock.iam.oauth2.application.configuration.client.OAuth2PostRequestBuilderConfig;
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.PkceCodeVerifierMethodClientConfig;
import com.airlock.iam.oauth2.domain.model.client.OAuth2ClientSideCredentials;
import com.airlock.iam.oauth2.domain.model.logging.HidingTokenRepresentation;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2TokenRepresentation;
import com.airlock.iam.plugin.api.annotation.license.PluginPropertyLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

public abstract class OAuth2ClientSettingsBase {
   public static final String AUTHORIZATION_REQUEST = "Authorization Request";
   public static final String ACCESS_TOKEN_REQUEST = "Access Token Request";
   public static final String AUTHENTICATION_AND_AUTHORIZATION = "Authentication and Authorization";
   public static final String RESOURCE_MAPPINGS = "Resource Mappings";
   public static final String INTEGRATION = "Integration";
   public static final String ACCOUNT_LINKING_AND_REGISTRATION = "Account Linking and Registration";
   @StringProperty(
      description = "Client ID identifying Airlock IAM at the authorization / token and resource endpoint of the OAuth 2.0 provider.<br/>Only alphanumeric characters and '-_.' are allowed.",
      exampleValues = {"example-app", "crypticyButUniqueAppId01953utjhu91823rih"},
      displayName = "Client ID",
      optional = false,
      validationRegex = "[a-zA-Z0-9-_.]+"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String clientId;
   @StringProperty(description = "Client secret used to verify the client.", isSensitive = true, optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String clientSecret;
   @PluginProperty(description = "HTTP method to use for Access Token requests.", optional = true, defaultValue = OAuth2PostRequestBuilderConfig.class)
   @GroupProperty(groupIdentifier = "Access Token Request")
   private OAuth2RequestBuilderConfig accessTokenRequestMethod;
   @PluginProperty(description = "Custom OAuth 2.0 client logging behaviour for integration or error diagnostics.", optional = true)
   @GroupProperty(groupIdentifier = "Integration")
   private OAuth2LoggingSettings loggingSettings;
   @BooleanProperty(
      description = "If enabled, this provider will solely function as an alternative authentication method for the accounts of the Loginapp's user store. Meaning that users having an IAM account and an account link to a provider account can authenticate using this provider. Account links can be created by <ul> <li>Users using the self-service</li> <li>The automated registration</li> <li>Auto-link feature</li></ul>",
      optional = true,
      displayName = "Enable Account Linking",
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Account Linking and Registration")
   @PluginPropertyLicenseRestriction(moduleAffiliation = {"OAuthAccountLinking", "OAuthSocialRegistration"})
   private boolean enableAccountLinking;
   public static final String ENABLE_ACCOUNT_LINKING_FIELD = "enableAccountLinking";
   public static final String ENABLE_ACCOUNT_LINKING_DISPLAY_NAME = "Enable Account Linking";
   @StringProperty(
      description = "If the provider's account has the same unique value for the given context data field as an existing account of the Loginapp's user persister, it will be linked with the provider's account. If left empty none of the existing accounts will be linked. <p>To be able to match the context data value, it is required to add an 'OAuth 2.0 Remote Context Data Resource' with a 'Local Context Data Key' equal to this value to the resource mappings <b>and</b> have a context data column entry equal to this value in the Loginapp's user persister.</p><p>If this feature is used in combination with 'Automated Account Registration', no accounts will be registered that have been auto-linked.</p><p><b>Security Warning:</b> For security reasons this should always be a context data field that is globally unique (e.g. email or phone number) and was previously verified by the IAM registration process (channel verification) <b>and</b> the provider's registration process. If this is not guaranteed, an attacker may be able to use this feature to log into a victim's IAM account.</p>",
      displayName = "Auto-link IAM Account Based on Context Data Field",
      suggestedValues = {"email", "mtan_number"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Account Linking and Registration")
   @PluginPropertyLicenseRestriction(moduleAffiliation = {"OAuthAccountLinking", "OAuthSocialRegistration"})
   private String autoLinkExistingUsersContextDataField;
   public static final String AUTO_LINK_EXISTING_USERS_CONTEXT_DATA_FIELD = "autoLinkExistingUsersContextDataField";
   private static final String AUTO_LINK_EXISTING_USERS_CONTEXT_DATA_FIELD_DISPLAY_NAME = "Auto-link IAM Account Based on Context Data Field";
   @PluginProperty(
      description = "Enables automated IAM account registration with data from this provider.<p>The user must always confirm the account registration.</p><p>If this feature is used in combination with 'Auto-link IAM Account Based on Context Data Field', no accounts will be registered that have been auto-linked.</p><p><b>Security Warning:</b> For automated account registration, the provider's data is used without additional validation. In particular: <ul> <li>Identity verification for mTAN numbers and/or email addresses is currently not supported.</li> <li>Data validation (e.g. using regular expressions) is currently not supported.</li> <li>The provider's data that is used to create the account is not displayed to the user and the user is not asked to confirm the data, e.g. using transaction approval.</li></ul>Therefore, if this feature is used, the provider must guarantee that the provided data is valid (e.g. identity-verified and validated). IAM must trust the provider to do appropriate validation.</p>",
      displayName = "Automated Account Registration",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Account Linking and Registration")
   @PluginPropertyLicenseRestriction(moduleAffiliation = "OAuthSocialRegistration")
   private AccountRegistrationConfig accountRegistrationConfig;
   public static final String AUTOMATED_ACCOUNT_REGISTRATION_FIELD = "accountRegistrationConfig";
   public static final String AUTOMATED_ACCOUNT_REGISTRATION_DISPLAY_NAME = "Automated Account Registration";

   public OAuth2ClientSideCredentials getClientCredentials() {
      return new OAuth2ClientSideCredentials(OAuth2ClientId.oAuth2ClientId(this.clientId), Optional.of(this.clientSecret));
   }

   public Optional<String> getAutoLinkExistingUsersContextDataField() {
      return Optional.ofNullable(this.autoLinkExistingUsersContextDataField);
   }

   public Optional<AccountRegistrationConfig> getAccountRegistrationConfig() {
      return Optional.ofNullable(this.accountRegistrationConfig);
   }

   public boolean isIntegrationLogEnabled() {
      return Optional.ofNullable(this.getLoggingSettings()).map(OAuth2LoggingSettings::isLogOnInfo).orElse(false);
   }

   public OAuth2TokenRepresentation tokenRepresentation() {
      return Optional.ofNullable(this.getLoggingSettings()).map(OAuth2LoggingSettings::tokenRepresentation).orElse(new HidingTokenRepresentation());
   }

   protected ELogger loggerFor(Class<?> clazz) {
      return IntegrationLogger.getLogger(clazz, this.isIntegrationLogEnabled());
   }

   protected static void validateLinkExistingSelfServiceConfig(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyConfigurationException {
      String linkExistingUser = StringPropertyValueReader.effectiveStringConfigValue(config, "autoLinkExistingUsersContextDataField", env, context);
      if (StringUtils.isNotBlank(linkExistingUser)) {
         assureAccountLinkingEnabled("autoLinkExistingUsersContextDataField", "Auto-link IAM Account Based on Context Data Field", config, env, context);
      }
   }

   protected static void validateAccountLinkingRegistrationConfig(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyConfigurationException {
      PluginConfiguration accountRegistration = PluginPropertyValueReader.effectivePluginPropertyValue(config, "accountRegistrationConfig", env, context);
      if (accountRegistration != null) {
         assureAccountLinkingEnabled("accountRegistrationConfig", "Automated Account Registration", config, env, context);
      }
   }

   protected static void assureAccountLinkingEnabled(
      String dependentPropertyField,
      String dependentPropertyDisplayName,
      PluginConfiguration config,
      ConfigurationEnvironment env,
      ConfigurationContext context
   ) throws PropertyConfigurationException {
      Boolean accountLinkingEnabled = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "enableAccountLinking", env, context);
      if (!accountLinkingEnabled) {
         throw new PropertyConfigurationException(
            config,
            env,
            context,
            "If '" + dependentPropertyDisplayName + "' is configured, it is required to enable 'Enable Account Linking'.",
            new String[]{"enableAccountLinking", dependentPropertyField}
         );
      }
   }

   public abstract PkceCodeVerifierMethodClientConfig pkceChallengeMethod();

   public abstract ProviderId getProviderId();

   public String getClientId() {
      return this.clientId;
   }

   public OAuth2RequestBuilderConfig getAccessTokenRequestMethod() {
      return this.accessTokenRequestMethod;
   }

   public OAuth2LoggingSettings getLoggingSettings() {
      return this.loggingSettings;
   }

   public boolean isEnableAccountLinking() {
      return this.enableAccountLinking;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2ClientSettingsBase(
      String clientId,
      String clientSecret,
      OAuth2RequestBuilderConfig accessTokenRequestMethod,
      OAuth2LoggingSettings loggingSettings,
      boolean enableAccountLinking,
      String autoLinkExistingUsersContextDataField,
      AccountRegistrationConfig accountRegistrationConfig
   ) {
      this.clientId = clientId;
      this.clientSecret = clientSecret;
      this.accessTokenRequestMethod = accessTokenRequestMethod;
      this.loggingSettings = loggingSettings;
      this.enableAccountLinking = enableAccountLinking;
      this.autoLinkExistingUsersContextDataField = autoLinkExistingUsersContextDataField;
      this.accountRegistrationConfig = accountRegistrationConfig;
   }
}
