package com.airlock.iam.plugin.framework.misc.frompersistence;

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.property.PropertyConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.config.AllPropertiesWalkerScope;
import com.airlock.iam.plugin.framework.misc.config.CombiningWalkerScope;
import com.airlock.iam.plugin.framework.misc.config.ConfigHistoryItem;
import com.airlock.iam.plugin.framework.misc.config.ParentWalkerScope;
import com.airlock.iam.plugin.framework.misc.config.SubtreeWalker;
import com.airlock.iam.plugin.framework.misc.config.modification.ConfigModificationItem;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginListPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginMapPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginPropertyConfiguration;
import com.airlock.iam.system.version.ConfigSchemaVersion;
import com.airlock.iam.system.version.IamVersion;
import com.google.common.collect.Sets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Map.Entry;

public class TranslatedMedusaConfiguration {
   private final Set<MutablePluginConfiguration> pool = Sets.newIdentityHashSet();
   private final boolean isDocumentTypeSnippet;
   private ConfigSchemaVersion schemaVersion;
   private IamVersion iamVersion;
   private final List<ConfigHistoryItem> history;
   private final List<ConfigModificationItem> modifications;

   public TranslatedMedusaConfiguration(
      List<MutablePluginConfiguration> configTreeRoots,
      boolean isDocumentTypeSnippet,
      ConfigSchemaVersion schemaVersion,
      @Nullable IamVersion iamVersion,
      List<ConfigHistoryItem> history,
      List<ConfigModificationItem> modifications
   ) {
      Objects.requireNonNull(configTreeRoots);
      Objects.requireNonNull(history);
      Objects.requireNonNull(modifications);
      this.isDocumentTypeSnippet = isDocumentTypeSnippet;
      this.schemaVersion = schemaVersion;
      this.iamVersion = iamVersion;
      this.history = history;
      this.modifications = modifications;
      this.createPoolFromConfigRoots(configTreeRoots);
   }

   public TranslatedMedusaConfiguration(List<MutablePluginConfiguration> configTreeRoots, boolean isDocumentTypeSnippet) {
      this(configTreeRoots, isDocumentTypeSnippet, ConfigSchemaVersion.UNDEFINED_SCHEMA, null, new ArrayList<>(), new ArrayList<>());
   }

   private void updatePool() {
      for (PluginConfiguration pc : this.pool.toArray(new PluginConfiguration[0])) {
         this.addPluginAndChildrenToPool(pc);
      }
   }

   private void createPoolFromConfigRoots(List<MutablePluginConfiguration> configTreeRoots) {
      for (PluginConfiguration pc : configTreeRoots) {
         this.addPluginAndChildrenToPool(pc);
      }
   }

   private void addPluginAndChildrenToPool(PluginConfiguration pc) {
      CombiningWalkerScope scope = CombiningWalkerScope.with().scope(new ParentWalkerScope()).scope(new AllPropertiesWalkerScope()).combine();
      Iterator<MutablePluginConfiguration> iter = new SubtreeWalker(pc, scope);

      while (iter.hasNext()) {
         this.pool.add(iter.next());
      }
   }

   private static void removeChildrenFromRoots(PluginConfiguration pc, ConfigurationContext context, Set<MutablePluginConfiguration> roots) {
      for (ConfigurationEnvironment env : pc.getLocallyUsedConfigurationEnvironments()) {
         for (PropertyConfiguration prop : pc.getDirectlyConfiguredProperties(env, context)) {
            if (prop instanceof PluginPropertyConfiguration pluginProperty) {
               roots.remove((MutablePluginConfiguration)pluginProperty.getValue());
            } else if (prop instanceof PluginListPropertyConfiguration pluginListProperty) {
               removePluginListMembersFromRoots(pluginListProperty, roots);
            } else if (prop instanceof PluginMapPropertyConfiguration pluginMapProperty) {
               removePluginMapMembersFromRoots(pluginMapProperty, roots);
            }
         }
      }
   }

   private static void removePluginMapMembersFromRoots(PluginMapPropertyConfiguration map, Set<MutablePluginConfiguration> roots) {
      if (map.getValue() != null) {
         for (PluginConfiguration pc : map.getValue().values()) {
            roots.remove((MutablePluginConfiguration)pc);
         }
      }
   }

   private static void removePluginListMembersFromRoots(PluginListPropertyConfiguration list, Set<MutablePluginConfiguration> roots) {
      if (list.getValue() != null) {
         for (PluginConfiguration pc : list.getValue()) {
            roots.remove((MutablePluginConfiguration)pc);
         }
      }
   }

   public void addPluginConfiguration(MutablePluginConfiguration pc) {
      this.pool.add(pc);
      this.updatePool();
   }

   public void removePluginConfiguration(PluginConfiguration pc) {
      this.updatePool();
      this.detachPluginConfiguration(pc);
      this.removeReferencesToParent(pc);
      this.pool.remove((MutablePluginConfiguration)pc);
   }

   private void removeReferencesToParent(PluginConfiguration toDetach) {
      for (MutablePluginConfiguration pc : this.pool) {
         if (pc.getParentConfiguration() == toDetach) {
            pc.removeParent();
         }
      }
   }

   private void detachPluginConfiguration(PluginConfiguration toDetach) {
      for (ConfigurationContext context : this.getConfigurationContexts()) {
         for (PluginConfiguration pc : this.pool) {
            detachPluginConfiguration(pc, toDetach, context);
         }
      }
   }

   private static void detachPluginConfiguration(PluginConfiguration fromPlugin, PluginConfiguration toDetach, ConfigurationContext context) {
      for (ConfigurationEnvironment env : fromPlugin.getLocallyUsedConfigurationEnvironments()) {
         for (PropertyConfiguration prop : fromPlugin.getDirectlyConfiguredProperties(env, context)) {
            if (prop instanceof PluginPropertyConfiguration && prop.getValue() == toDetach) {
               ((PluginPropertyConfiguration)prop).setValue(null);
            } else if (prop instanceof PluginListPropertyConfiguration) {
               removeFromPluginList(toDetach, (PluginListPropertyConfiguration)prop);
            } else if (prop instanceof PluginMapPropertyConfiguration) {
               removeFromPluginMap(toDetach, (PluginMapPropertyConfiguration)prop);
            }
         }
      }
   }

   private static void removeFromPluginMap(PluginConfiguration toDetach, PluginMapPropertyConfiguration prop) {
      Map<String, PluginConfiguration> map = prop.getValue();
      if (map != null) {
         String key = null;

         for (Entry<String, PluginConfiguration> mapEntry : map.entrySet()) {
            if (mapEntry.getValue() == toDetach) {
               key = mapEntry.getKey();
               break;
            }
         }

         if (key != null) {
            map.remove(key);
         }
      }
   }

   private static void removeFromPluginList(PluginConfiguration toDetach, PluginListPropertyConfiguration prop) {
      List<PluginConfiguration> listElems = prop.getValue();
      if (listElems != null) {
         listElems.remove(toDetach);
      }
   }

   public Set<MutablePluginConfiguration> getConfigTreeRoots() {
      Set<MutablePluginConfiguration> roots = Sets.newIdentityHashSet();
      roots.addAll(this.pool);
      Set<ConfigurationContext> allContexts = this.getConfigurationContexts();

      for (PluginConfiguration pc : this.pool) {
         for (ConfigurationContext context : allContexts) {
            removeChildrenFromRoots(pc, context, roots);
         }

         if (pc.hasParentConfiguration()) {
            roots.remove((MutablePluginConfiguration)pc.getParentConfiguration());
         }
      }

      return roots;
   }

   @Nullable
   public PluginConfiguration getByDisplayName(String displayName) {
      this.updatePool();

      for (PluginConfiguration pc : this.pool) {
         if (displayName.equals(pc.getPluginDisplayName())) {
            return pc;
         }
      }

      return null;
   }

   @Nullable
   @Deprecated(since = "8.5")
   public PluginConfiguration getByIdentifier(String displayName) {
      return this.getByDisplayName(displayName);
   }

   public Set<PluginConfiguration> getAllPluginConfigurations() {
      this.updatePool();
      Set<PluginConfiguration> set = Sets.newIdentityHashSet();
      set.addAll(this.pool);
      return set;
   }

   public void addHistoryItem(ConfigHistoryItem historyItem) {
      this.history.add(historyItem);
   }

   public void addModificationItem(ConfigModificationItem modificationItem) {
      this.modifications.add(modificationItem);
   }

   public Set<ConfigurationContext> getConfigurationContexts() {
      this.updatePool();
      Set<ConfigurationContext> contexts = new HashSet<>();
      contexts.add(ConfigurationContext.defaultContext());

      for (MutablePluginConfiguration pc : this.pool) {
         pc.addLocallyUsedConfigurationContextsInAllEnvironments(contexts);
      }

      for (ConfigModificationItem item : this.modifications) {
         item.targetContext().ifPresent(ctx -> contexts.add(ConfigurationContext.configurationContext(ctx)));
      }

      return contexts;
   }

   public Set<ConfigurationEnvironment> getConfigurationEnvironments() {
      this.updatePool();
      Set<ConfigurationEnvironment> environments = new HashSet<>();
      environments.add(ConfigurationEnvironment.common());

      for (PluginConfiguration pc : this.pool) {
         environments.addAll(pc.getLocallyUsedConfigurationEnvironments());
      }

      for (ConfigModificationItem item : this.modifications) {
         item.targetEnv().ifPresent(env -> environments.add(ConfigurationEnvironment.configurationEnvironment(env)));
      }

      return environments;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isDocumentTypeSnippet() {
      return this.isDocumentTypeSnippet;
   }

   @SuppressFBWarnings(justification = "generated code")
   public ConfigSchemaVersion getSchemaVersion() {
      return this.schemaVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setSchemaVersion(ConfigSchemaVersion schemaVersion) {
      this.schemaVersion = schemaVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public IamVersion getIamVersion() {
      return this.iamVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setIamVersion(IamVersion iamVersion) {
      this.iamVersion = iamVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public List<ConfigHistoryItem> getHistory() {
      return this.history;
   }

   @SuppressFBWarnings(justification = "generated code")
   public List<ConfigModificationItem> getModifications() {
      return this.modifications;
   }
}
