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

import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.common.infrastructure.usertrail.UserTrailLogDatabaseAppender;
import com.airlock.iam.core.misc.plugin.config.activation.ConfigActivator;
import com.airlock.iam.core.misc.plugin.config.activation.ConfigurationRootContainer;
import com.airlock.iam.core.misc.util.config.ActiveLicenseEnvironment;
import com.airlock.iam.core.misc.util.config.Config;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.usertrail.UserTrailLogger;
import com.airlock.iam.login.app.misc.configuration.Loginapp;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.framework.application.service.license.LicenseManager;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.airlock.iam.saml2.infrastructure.util.Saml2LoginappConfigProvider;
import com.airlock.iam.system.property.IamSystemProperties;
import com.airlock.iam.system.version.CurrentVersion;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.ServletException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class Configuration implements ConfigurationRootContainer {
   private static final ELogger LOG = ELoggerFactory.getLogger(Configuration.class);
   private Map<ConfigurationContext, Loginapp> loginapps;
   private final Supplier<ConfigActivator> configActivator = Suppliers.memoize(
      () -> new ConfigActivator(
         Loginapp.LOGINAPP_CONFIG_IDENTIFIER,
         ConfigActivator.ContextHandling.ALL_CONTEXTS,
         IamSystemProperties.getActivatedConfigsDirectory(),
         ActiveLicenseEnvironment.activeLicenseManager()
      )
   );
   private static Configuration instance;
   private boolean licensed;

   @VisibleForTesting
   protected Configuration() {
      Config.logAuditLoggerConfigFileHash();
      this.loginapps = new HashMap<>();
   }

   public static Configuration getInstance() {
      if (instance == null) {
         throw new IllegalStateException("configuration not initialized");
      } else {
         return instance;
      }
   }

   @Override
   public boolean isLicensed() {
      return this.licensed;
   }

   @Override
   public void setLicensed(boolean licensed) {
      this.licensed = licensed;
   }

   @Override
   public void activate(Map<ConfigurationContext, Object> configRoots) {
      if (Config.setActive(true)) {
         LOG.info(" ***** AIRLOCK LOGINAPP MODULE: ACTIVATING *****");
         LOG.info("Version: " + CurrentVersion.getDisplayString());
      }

      this.initLoginApps(configRoots);
      this.initSaml2LoginappConfigProviders();
      this.initUserTrailLogAppenders();
   }

   @Override
   public void deactivate() {
      if (Config.setActive(false)) {
         LOG.info(" ***** AIRLOCK LOGINAPP MODULE: DEACTIVATING *****");
         LOG.info("Version: " + CurrentVersion.getDisplayString());
         UserTrailLogger.unhookAllUserTrailLogAppenders();
         getInstance().getAllLoginappContexts().forEach(ctx -> PluginLoader.unloadPluginTree(getInstance().getLoginApp(ctx)));
         LOG.debug(" ***** AIRLOCK LOGINAPP MODULE: INACTIVE *****");
      }
   }

   private Set<ConfigurationContext> getAllLoginappContexts() {
      return Set.copyOf(this.loginapps.keySet());
   }

   public void checkConfigurationLoaded() throws ServletException {
      if (getInstance().getLoginApp(null) == null) {
         throw new ServletException("Configuration of Loginapp not loaded!");
      }
   }

   public LicenseManager getLicenseManager() {
      return this.getConfigActivator().getLicenseManger();
   }

   public ConfigActivator getConfigActivator() {
      return this.configActivator.get();
   }

   public Loginapp getLoginApp(ConfigurationContext context) {
      return this.loginapps.getOrDefault(context, this.loginapps.get(ConfigurationContext.defaultContext()));
   }

   @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2")
   @VisibleForTesting
   public static void setInstance(Configuration configuration) {
      instance = Objects.requireNonNull(configuration);
   }

   static void initialize() throws RuntimeException {
      instance = new Configuration();
      Config.setInstance(Loginapp.LOGINAPP_CONFIG_IDENTIFIER, configurationContext -> Loginapp.get(configurationContext).getSessionIdleTimeout());
      instance.initConfigActivator();
   }

   private void initConfigActivator() {
      this.getConfigActivator().loadPluginTreeAsync(instance);
      this.getConfigActivator().autoActivateOnConfigFileChanges();
   }

   private void initLoginApps(Map<ConfigurationContext, Object> configRoots) {
      this.loginapps = configRoots.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> (Loginapp)e.getValue()));
   }

   private void initSaml2LoginappConfigProviders() {
      Saml2LoginappConfigProvider.setSamlConfigProvider(context -> this.getLoginApp(context).getSamlSettings());
      Saml2LoginappConfigProvider.setUserStoreProvider(context -> this.getLoginApp(context).getUserStore());
      Saml2LoginappConfigProvider.setConfigContextsProvider(() -> Set.copyOf(this.loginapps.keySet()));
   }

   private void initUserTrailLogAppenders() {
      UserTrailLogger.unhookAllUserTrailLogAppenders();

      for (Entry<ConfigurationContext, Loginapp> entry : this.loginapps.entrySet()) {
         if (entry.getKey().isDefaultContext()) {
            this.initDefaultContextAppender(entry.getValue());
         } else {
            this.initAdditionalContextAppenders(entry.getValue(), entry.getKey());
         }
      }
   }

   private void initDefaultContextAppender(Loginapp rootConfig) {
      rootConfig.getUserTrailLogRepositoryConfig()
         .map(UserTrailLogRepositoryConfig::getRepository)
         .ifPresent(repository -> UserTrailLogger.defineUserTrailLogAppenderForDefaultContext(new UserTrailLogDatabaseAppender(repository)));
   }

   private void initAdditionalContextAppenders(Loginapp rootConfig, ConfigurationContext context) {
      rootConfig.getUserTrailLogRepositoryConfig()
         .map(UserTrailLogRepositoryConfig::getRepository)
         .ifPresent(repository -> UserTrailLogger.defineUserTrailLogAppenderForContext(context.getIdentifier(), new UserTrailLogDatabaseAppender(repository)));
   }
}
