package com.airlock.iam.login.app.misc.configuration;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.oauth2.application.configuration.claims.GenericCustomClaimConfig;
import com.airlock.iam.oauth2.application.configuration.token.JwtAccessTokenFormatConfig;
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.ConfigurationException;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class OAuth2CustomClaimsPersistenceValidator {
   private OAuth2CustomClaimsPersistenceValidator() {
   }

   public static void checkWhetherPersistedCustomClaimsAreConfiguredInOAuth2AndOidcFlows(
      PluginConfiguration asConfig, ConfigurationEnvironment env, ConfigurationContext context
   ) throws ConfigurationException {
      boolean persistClaims = BooleanPropertyValueReader.effectiveBooleanPropertyValue(asConfig, "persistClaims", env, context);
      if (!persistClaims) {
         checkOAuth2AndOidcFlows(asConfig, env, context);
         checkStaticClients(asConfig, env, context);
      }
   }

   private static void checkOAuth2AndOidcFlows(PluginConfiguration asConfig, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      PluginConfiguration grantsConfig = PluginPropertyValueReader.effectivePluginPropertyValue(asConfig, "oauth2Grants", env, context);
      if (grantsConfig != null) {
         PluginConfiguration authorizationCodeGrantConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
            grantsConfig, "authorizationCodeGrant", env, context
         );
         if (authorizationCodeGrantConfig != null) {
            PluginConfiguration accessTokenClaimConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
               authorizationCodeGrantConfig, "accessTokenFormat", env, context
            );
            checkCustomClaimsInAccessToken(env, context, accessTokenClaimConfig, authorizationCodeGrantConfig);
         }

         PluginConfiguration oidcAuthorizationCodeGrantConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
            grantsConfig, "oidcAuthorizationCodeGrant", env, context
         );
         if (oidcAuthorizationCodeGrantConfig != null) {
            PluginConfiguration accessTokenClaimConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
               oidcAuthorizationCodeGrantConfig, "accessTokenFormat", env, context
            );
            checkCustomClaimsInAccessToken(env, context, accessTokenClaimConfig, oidcAuthorizationCodeGrantConfig);
            PluginConfiguration idTokenClaimConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
               oidcAuthorizationCodeGrantConfig, "idToken", env, context
            );
            checkCustomClaimsInIdToken(env, context, idTokenClaimConfig);
            PluginConfiguration hybridFlowAccessTokenClaimConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
               oidcAuthorizationCodeGrantConfig, "hybridFlowAccessTokenFormat", env, context
            );
            checkCustomClaimsInAccessToken(env, context, hybridFlowAccessTokenClaimConfig, oidcAuthorizationCodeGrantConfig);
            PluginConfiguration hybridFlowIdTokenClaimConfig = PluginPropertyValueReader.effectivePluginPropertyValue(
               oidcAuthorizationCodeGrantConfig, "hybridFlowIdToken", env, context
            );
            checkCustomClaimsInIdToken(env, context, hybridFlowIdTokenClaimConfig);
         }
      }
   }

   private static void checkStaticClients(PluginConfiguration asConfig, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      PluginConfiguration staticClientConfig = PluginPropertyValueReader.effectivePluginPropertyValue(asConfig, "staticClients", env, context);
      if (staticClientConfig != null) {
         List<PluginConfiguration> staticClientConfigs = PluginListPropertyValueReader.effectivePluginListPropertyValue(
            staticClientConfig, "staticClients", env, context
         );
         if (!staticClientConfigs.isEmpty()) {
            for (PluginConfiguration staticClient : staticClientConfigs) {
               List<PluginConfiguration> accessTokenCustomClaims = PluginListPropertyValueReader.effectivePluginListPropertyValue(
                  staticClient, "accessTokenCustomClaims", env, context
               );
               checkListOfCustomClaims(env, context, staticClient, accessTokenCustomClaims, "accessTokenCustomClaims");
               List<PluginConfiguration> idTokenCustomClaims = PluginListPropertyValueReader.effectivePluginListPropertyValue(
                  staticClient, "idTokenCustomClaims", env, context
               );
               checkListOfCustomClaims(env, context, staticClient, idTokenCustomClaims, "idTokenCustomClaims");
            }
         }
      }
   }

   private static void checkCustomClaimsInAccessToken(
      ConfigurationEnvironment env, ConfigurationContext context, PluginConfiguration accessTokenClaimConfig, PluginConfiguration authorizationCodeGrantConfig
   ) throws ConfigurationException {
      if (accessTokenClaimConfig != null) {
         try {
            if (JwtAccessTokenFormatConfig.class.isAssignableFrom(accessTokenClaimConfig.getPluginClass())) {
               List<PluginConfiguration> accessTokenCustomClaims = PluginListPropertyValueReader.effectivePluginListPropertyValue(
                  accessTokenClaimConfig, "customClaims", env, context
               );
               checkListOfCustomClaims(env, context, accessTokenClaimConfig, accessTokenCustomClaims, "customClaims");
            }
         } catch (InitializeException e) {
            throw new ConfigurationException(authorizationCodeGrantConfig, env, context, "Cannot validate configuration: " + e.getMessage(), e);
         }
      }
   }

   private static void checkCustomClaimsInIdToken(ConfigurationEnvironment env, ConfigurationContext context, PluginConfiguration hybridFlowIdTokenClaimConfig) throws ConfigurationException {
      if (hybridFlowIdTokenClaimConfig != null) {
         PluginConfiguration oidcIdTokenClaims = PluginPropertyValueReader.effectivePluginPropertyValue(hybridFlowIdTokenClaimConfig, "claims", env, context);
         if (oidcIdTokenClaims != null) {
            List<PluginConfiguration> idTokenCustomClaims = PluginListPropertyValueReader.effectivePluginListPropertyValue(
               oidcIdTokenClaims, "customClaims", env, context
            );
            checkListOfCustomClaims(env, context, oidcIdTokenClaims, idTokenCustomClaims, "customClaims");
         }
      }
   }

   private static void checkListOfCustomClaims(
      ConfigurationEnvironment env,
      ConfigurationContext context,
      PluginConfiguration parentConfigOfCustomClaimConfigList,
      List<PluginConfiguration> customClaims,
      String propertyNameOfCustomClaimList
   ) throws ConfigurationException {
      if (!customClaims.isEmpty()) {
         checkCustomClaimConfigForInvalidConfigurationCombination(
            env, context, customClaims, parentConfigOfCustomClaimConfigList, propertyNameOfCustomClaimList
         );
      }
   }

   private static void checkCustomClaimConfigForInvalidConfigurationCombination(
      ConfigurationEnvironment env,
      ConfigurationContext context,
      List<PluginConfiguration> customClaimsConfigs,
      PluginConfiguration configLevelWithTheInvalidConfiguration,
      String propertyNameWithInvalidConfig
   ) throws ConfigurationException {
      Set<String> persistenceRequiringCustomClaimNames = new HashSet<>();

      try {
         for (PluginConfiguration customClaimPlugin : customClaimsConfigs) {
            if (!GenericCustomClaimConfig.class.isAssignableFrom(customClaimPlugin.getPluginClass())) {
               persistenceRequiringCustomClaimNames.add(customClaimPlugin.getPluginClassDisplayName());
            }
         }
      } catch (InitializeException e) {
         throw new ConfigurationException(configLevelWithTheInvalidConfiguration, env, context, "Cannot validate configuration: " + e.getMessage(), e);
      }

      if (!persistenceRequiringCustomClaimNames.isEmpty()) {
         String invalidPlugins = String.join(", ", persistenceRequiringCustomClaimNames);
         throw new PropertyConstraintViolationException(
            propertyNameWithInvalidConfig,
            configLevelWithTheInvalidConfiguration,
            env,
            context,
            "The following custom claim plugins require \"Persist Claims\" enabled: " + LogUtil.quote(invalidPlugins)
         );
      }
   }
}
