package com.airlock.iam.plugin.configeditor;

import com.airlock.iam.configeditor.shared.application.ConfigEditorProperties;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.configeditor.data.ConfigContainer;
import com.airlock.iam.plugin.configeditor.ui.dialog.WorkOnDraftDialog;
import com.airlock.iam.plugin.framework.misc.config.ConfigHistoryItem;
import com.airlock.iam.system.config.ConfigFormat;
import com.airlock.iam.system.property.IamSystemProperties;
import java.io.File;
import java.nio.file.Path;
import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.io.FileUtils;

public final class ConfigLoader {
   private static final ELogger LOG = ELoggerFactory.getLogger(ConfigLoader.class);
   private Duration activationTimeout = ConfigEditorProperties.configEditorProperties().getActivationTimeout();
   private Optional<Path> configFileToLoad = Optional.empty();
   private Path workingCopyDirectory;
   private final List<ConfigurationLoadListener> listeners = new LinkedList<>();
   private boolean initialized;
   private Object lastEvent;

   public void loadActiveConfig() {
      if (!this.initialized) {
         this.setConfigFileToLoad(IamSystemProperties.getPluginConfig());
         this.loadConfigFromFileAndSetInitialized(IamSystemProperties.activeConfigFormat());
      } else {
         this.refireLastEvent();
      }
   }

   public void loadDetachedConfig(File configFile, ConfigFormat configFormat, boolean loadDraftIfAvailable, boolean deleteConfigAfterLoad) {
      Objects.requireNonNull(configFile, "configFile null");
      ConfigContainer localConfigContainer = new ConfigContainer(configFile);
      if (loadDraftIfAvailable && localConfigContainer.isDraftAvailableAndNewerThanConfig()) {
         try {
            Optional<ConfigHistoryItem> draftMetaData = localConfigContainer.getDraftConfigFileMetaData();
            WorkOnDraftDialog confirmDialog = new WorkOnDraftDialog(draftMetaData);
            confirmDialog.addCloseListener(
               closeEvent -> {
                  if (confirmDialog.isConfirmed()) {
                     this.loadAndFireEvent(
                        configFile, configFormat, localConfigContainer, ConfigurationLoadListener.ConfigType.DRAFT, true, deleteConfigAfterLoad
                     );
                  } else if (confirmDialog.isCancelled()) {
                     localConfigContainer.deleteDraft();
                     this.loadAndFireEvent(
                        configFile, configFormat, localConfigContainer, ConfigurationLoadListener.ConfigType.ACTIVE, false, deleteConfigAfterLoad
                     );
                  }
               }
            );
         } catch (InitializeException e) {
            LOG.error(LogTypes.CONFIGURATION_ERROR, "Could not read meta data of draft config file: " + e.getMessage(), e);
            this.fireFailedToLoadConfigurationEvent(e);
         }
      } else {
         this.loadAndFireEvent(
            configFile, configFormat, localConfigContainer, ConfigurationLoadListener.ConfigType.ACTIVE, loadDraftIfAvailable, deleteConfigAfterLoad
         );
      }
   }

   private void loadAndFireEvent(
      File configFile,
      ConfigFormat configFormat,
      ConfigContainer localConfigContainer,
      ConfigurationLoadListener.ConfigType configType,
      boolean loadDraftIfAvailable,
      boolean deleteConfigAfterLoad
   ) {
      try {
         localConfigContainer.loadConfig(configFormat, loadDraftIfAvailable);
         this.fireConfigurationLoadedEvent(configType, localConfigContainer);
      } catch (InitializeException e) {
         String configTypeString = configType == ConfigurationLoadListener.ConfigType.ACTIVE ? "active" : "draft";
         LOG.error(LogTypes.CONFIGURATION_ERROR, "Could not load " + configTypeString + " config: " + e.getMessage(), e);
         this.fireFailedToLoadConfigurationEvent(e);
      } finally {
         if (deleteConfigAfterLoad) {
            FileUtils.deleteQuietly(configFile);
         }
      }
   }

   public void addConfigurationLoadListener(ConfigurationLoadListener listener) {
      if (listener != null && !this.listeners.contains(listener)) {
         this.listeners.add(listener);
      }
   }

   public void removeConfigurationLoadListener(ConfigurationLoadListener listener) {
      this.listeners.remove(listener);
   }

   public void fireConfigurationLoadedEvent(ConfigurationLoadListener.ConfigType configType, ConfigContainer handler) {
      ConfigurationLoadListener.ConfigurationLoadedEvent event = new ConfigurationLoadListener.ConfigurationLoadedEvent(configType, handler);
      this.fireConfigurationLoadedEvent(event);
      this.lastEvent = event;
   }

   public void fireFailedToLoadConfigurationEvent(InitializeException e) {
      ConfigurationLoadListener.FailedToLoadConfigurationEvent event = new ConfigurationLoadListener.FailedToLoadConfigurationEvent(e);
      this.fireFailedToLoadConfigurationEvent(event);
      this.lastEvent = event;
   }

   private void fireConfigurationLoadedEvent(ConfigurationLoadListener.ConfigurationLoadedEvent event) {
      for (ConfigurationLoadListener listener : this.listeners) {
         listener.newConfigurationLoaded(event);
      }
   }

   private void fireFailedToLoadConfigurationEvent(ConfigurationLoadListener.FailedToLoadConfigurationEvent event) {
      for (ConfigurationLoadListener listener : this.listeners) {
         listener.failedToLoadConfig(event);
      }
   }

   private void refireLastEvent() {
      switch (this.lastEvent) {
         case null:
            break;
         case ConfigurationLoadListener.ConfigurationLoadedEvent configurationLoadedEvent:
            configurationLoadedEvent.setRefired();
            this.fireConfigurationLoadedEvent(configurationLoadedEvent);
            break;
         case ConfigurationLoadListener.FailedToLoadConfigurationEvent failedToLoadConfigurationEvent:
            this.fireFailedToLoadConfigurationEvent(failedToLoadConfigurationEvent);
            break;
         default:
            throw new IllegalStateException("no supported event found to refire: " + this.lastEvent);
      }
   }

   private void loadConfigFromFileAndSetInitialized(ConfigFormat configFormat) {
      File configFile = this.configFileToLoad.orElseThrow().toFile();
      if (this.isFileAvailable(configFile)) {
         this.loadDetachedConfig(configFile, configFormat, true, false);
         this.initialized = true;
      }
   }

   private boolean isFileAvailable(File cf) {
      return cf.isFile() && cf.canRead();
   }

   public void setActivationTimeout(Duration timeout) {
      this.activationTimeout = timeout;
   }

   private void setConfigFileToLoad(Path fileToLoad) {
      this.configFileToLoad = Optional.ofNullable(fileToLoad);
      this.workingCopyDirectory = this.configFileToLoad.<Path>map(file -> file.resolveSibling("workingcopies")).orElse(null);
   }

   public Optional<String> getConfigFileToLoad() {
      return this.configFileToLoad.map(Path::toString);
   }

   public String getWorkingCopyDirectory() {
      return Optional.ofNullable(this.workingCopyDirectory).map(Path::toString).orElse("workingcopies");
   }

   public Duration getActivationTimeout() {
      return this.activationTimeout;
   }
}
