package com.airlock.iam.servicecontainer.app.infrastructure.server;

import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.common.domain.model.usertrail.UserTrailLogRepository;
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.plugin.config.activation.ConfigActivator.ContextHandling;
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.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.airlock.iam.servicecontainer.app.application.configuration.Server;
import com.airlock.iam.servicecontainer.app.infrastructure.webapp.view.ServiceContainerUiUpdaterThread;
import com.airlock.iam.system.property.IamSystemProperties;
import com.airlock.iam.system.version.CurrentVersion;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

public final class Controller implements ConfigurationRootContainer {
   private static final int TIMEOUT = 5000;
   private Optional<Server> server = Optional.empty();
   private final Supplier<ConfigActivator> configActivator = Suppliers.memoize(
      () -> new ConfigActivator(
         Server.SERVICE_CONTAINER_CONFIG_IDENTIFIER,
         ContextHandling.DEFAULT_CONTEXT_ONLY,
         IamSystemProperties.getActivatedConfigsDirectory(),
         ActiveLicenseEnvironment.activeLicenseManager()
      )
   );
   private ServiceContainerUiUpdaterThread uiUpdaterThread;
   private static Controller instance;
   private boolean licensed;
   private static final ELogger LOG = ELoggerFactory.getLogger(Controller.class);

   private Controller() {
   }

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

   public Server getServer() {
      return this.server.orElseThrow(() -> new IllegalStateException("Servicecontainerapp config not yet activated."));
   }

   public static void initialize() {
      instance = new Controller();
      Config.setInstance(Server.SERVICE_CONTAINER_CONFIG_IDENTIFIER, configurationContext -> getInstance().getServer().getSessionTimeout());
      instance.initConfigActivator();
   }

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

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

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

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

   public void activate(Map<ConfigurationContext, Object> configRoots) {
      if (Config.setActive(true)) {
         this.uiUpdaterThread = new ServiceContainerUiUpdaterThread();
         this.uiUpdaterThread.setDaemon(true);
         this.uiUpdaterThread.start();
         LOG.info(" ***** SERVICE CONTAINER MODULE: ACTIVATING *****");
         LOG.info("Version: " + CurrentVersion.getDisplayString());
      }

      this.setConfigurationRoots(configRoots);
      this.initUserTrailLogAppenders();
   }

   private void setConfigurationRoots(Map<ConfigurationContext, Object> configRoots) {
      Server newServer = (Server)configRoots.get(ConfigurationContext.defaultContext());
      Preconditions.checkState(
         this.server.isEmpty() || this.server.get() == newServer, "Cannot override active Servicecontainerapp config before old one is deactivated."
      );
      this.server = Optional.of(newServer);
   }

   public void deactivate() {
      if (Config.setActive(false)) {
         this.uiUpdaterThread.stopAndWait();
         this.uiUpdaterThread = null;
         LOG.info(" ***** SERVICE CONTAINER MODULE: DEACTIVATING *****");
         LOG.info("Version: " + CurrentVersion.getDisplayString());
         if (this.server.isPresent()) {
            Server oldServer = this.server.get();
            oldServer.stopAllServices(5000L);
            UserTrailLogger.unhookAllUserTrailLogAppenders();
            PluginLoader.unloadPluginTree(oldServer);
            this.server = Optional.empty();
         }

         LOG.debug(" ***** SERVICE CONTAINER MODULE: INACTIVE *****");
      }
   }

   public ServiceContainerUiUpdaterThread getUiUpdaterThread() {
      return this.uiUpdaterThread;
   }

   private void initUserTrailLogAppenders() {
      UserTrailLogger.unhookAllUserTrailLogAppenders();
      this.server.ifPresent(this::initDefaultContextAppender);
   }

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