package com.airlock.iam.admin.application.configuration.users;

import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.core.infrastructure.store.user.persister.UserPersisterUserStoreFactory;
import com.airlock.iam.core.misc.persistency.ExtendedUserPersister;
import com.airlock.iam.core.misc.persistency.UserIterator;
import com.airlock.iam.core.misc.persistency.UserPersister;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
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.InitializeException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;

@Plugin(description = "Configuration of user persister settings to access users in the admintool.", displayName = "User Persister Configuration")
public class UserPersisterConfiguration extends UserRepositoryConfiguration {
   public static final String USER_PERSISTER_CONFIGURATION_DISPLAY_NAME = "User Persister Configuration";
   @PluginProperty(
      description = "Persister plugin used to load and store users from a database, directory, or alike.<p>If the property \"User Iterator\" is not defined, this plugin is also used for listing and finding users.</p><p>Depending on the configured features, the plugin must be able to insert and delete users (i.e. implement the \"ExtendedUserPersister\" interface).",
      optional = false
   )
   private UserPersister userPersister;
   private static final String USER_PERSISTER_FIELD = "userPersister";
   @PluginProperty(
      description = "Persister plugin used to list and search users querying a database, directory, or alike.<p>This property is not required unless a specially configured persister is required to list and search users.</p>",
      optional = true
   )
   private UserIterator userIterator;
   private static final String USER_ITERATOR_FIELD = "userIterator";
   @PluginProperty(
      description = "Persister plugin used to load user data for the user list page: Normally, user data is loaded using the user persister plugin defined in property \"User Persister\". However, this can be inefficient, if a lot of data is loaded that is not needed for the user list page. <br/>By defining a differently configured persister plugin (or different plugin), loading user data for the user list page can be sped up. ",
      optional = true
   )
   private UserPersister userListDetailLoader;
   private UserStore userStore;

   public UserPersisterConfiguration(UserPersister userPersister, UserIterator userIterator, UserPersister userListDetailLoader) {
      this.userPersister = userPersister;
      this.userIterator = userIterator;
      this.userListDetailLoader = userListDetailLoader;
   }

   @AfterConfigActivation
   void createUserStore() {
      this.userStore = UserPersisterUserStoreFactory.userStoreFrom(this.userPersister, Optional.ofNullable(this.userIterator));
   }

   @ConfigurationValidator
   public static void checkConfig(PluginConfiguration conf, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      assureIteratorPlugin(conf, env, context);
   }

   private static void assureIteratorPlugin(PluginConfiguration conf, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      PluginConfiguration iteratorConf = PluginPropertyValueReader.effectivePluginPropertyValue(conf, "userIterator", env, context);
      if (iteratorConf == null) {
         PluginConfiguration persisterConf = PluginPropertyValueReader.effectivePluginPropertyValue(conf, "userPersister", env, context);

         Class<?> userPersisterPluginClass;
         try {
            userPersisterPluginClass = persisterConf.getPluginClass();
         } catch (InitializeException e) {
            throw new PropertyConstraintViolationException(
               "userPersister",
               conf,
               env,
               context,
               "Could not load class \""
                  + persisterConf.getPluginClassName()
                  + "\" of configured user persister plugin (property \"userPersister\"): "
                  + e.getMessage(),
               e
            );
         }

         if (!UserIterator.class.isAssignableFrom(userPersisterPluginClass)) {
            throw new PropertyConstraintViolationException(
               "userIterator",
               conf,
               env,
               context,
               "If no user iterator is defined, the user persister configured in property \"userPersister\" must implement the \""
                  + UserIterator.class.getSimpleName()
                  + "\" interface."
            );
         }
      }
   }

   public static boolean isUserPersisterExtended(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws InitializeException {
      PluginConfiguration upc = PluginPropertyValueReader.effectivePluginPropertyValue(config, "userPersister", env, context);
      return upc == null ? false : ExtendedUserPersister.class.isAssignableFrom(upc.getPluginClass());
   }

   @Override
   public UserStore getUserStore() {
      return this.userStore;
   }

   @Override
   public Optional<UserStore> getUserListDetailLoader() {
      return Optional.ofNullable(this.userListDetailLoader)
         .or(() -> Optional.ofNullable(this.userIterator).filter(UserPersister.class::isInstance).map(UserPersister.class::cast))
         .or(() -> Optional.of(this.userPersister))
         .map(UserPersisterUserStoreFactory::userStoreFrom);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static UserPersisterConfiguration.UserPersisterConfigurationBuilder builder() {
      return new UserPersisterConfiguration.UserPersisterConfigurationBuilder();
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class UserPersisterConfigurationBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private UserPersister userPersister;
      @SuppressFBWarnings(justification = "generated code")
      private UserIterator userIterator;
      @SuppressFBWarnings(justification = "generated code")
      private UserPersister userListDetailLoader;

      @SuppressFBWarnings(justification = "generated code")
      UserPersisterConfigurationBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserPersisterConfiguration.UserPersisterConfigurationBuilder userPersister(UserPersister userPersister) {
         this.userPersister = userPersister;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserPersisterConfiguration.UserPersisterConfigurationBuilder userIterator(UserIterator userIterator) {
         this.userIterator = userIterator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserPersisterConfiguration.UserPersisterConfigurationBuilder userListDetailLoader(UserPersister userListDetailLoader) {
         this.userListDetailLoader = userListDetailLoader;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserPersisterConfiguration build() {
         return new UserPersisterConfiguration(this.userPersister, this.userIterator, this.userListDetailLoader);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "UserPersisterConfiguration.UserPersisterConfigurationBuilder(userPersister="
            + this.userPersister
            + ", userIterator="
            + this.userIterator
            + ", userListDetailLoader="
            + this.userListDetailLoader
            + ")";
      }
   }
}
