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.DefaultPluginConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.domain.model.ConfigHistoryMetaDataItem;
import com.airlock.iam.plugin.framework.domain.model.MedusaConfiguration;
import com.airlock.iam.plugin.framework.domain.model.Plugin;
import com.airlock.iam.plugin.framework.domain.model.property.Property;
import com.airlock.iam.plugin.framework.misc.config.ConfigHistoryItem;
import com.airlock.iam.plugin.framework.misc.config.modification.ConfigModificationItem;
import com.airlock.iam.system.version.ConfigSchemaVersion;
import com.airlock.iam.system.version.IamVersion;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

public class PersistenceModelToPCModelTranslation {
   public static final IamVersion OLDEST_VALID_VERSION = IamVersion.fromString("7.1");

   public TranslatedMedusaConfiguration translate(MedusaConfiguration config) throws PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      TranslationState translation = this.translateToPluginConfigurations(config);
      initializeUUIDRelationships(translation);
      initializeTemplateRelationships(translation);
      initializePropertyRelationships(translation);
      List<MutablePluginConfiguration> configRoots = topLevelPlugins(config, translation);
      return new TranslatedMedusaConfiguration(
         configRoots,
         isDocumentTypeSnippet(config),
         getConfigSchemaVersion(config),
         getIamVersion(config),
         this.getConfigurationHistory(config),
         this.getModifications(config)
      );
   }

   private List<ConfigModificationItem> getModifications(MedusaConfiguration config) {
      return config.getConfigurationElement().getModifications().stream().map(ConfigModificationToConfigModificationItemTransformer::transform).toList();
   }

   private List<ConfigHistoryItem> getConfigurationHistory(MedusaConfiguration config) {
      List<ConfigHistoryItem> history = new ArrayList<>();

      for (ConfigHistoryMetaDataItem item : config.getConfigurationElement().getHistory()) {
         history.add(new ConfigHistoryItem(item.getAuthor().orElse(null), item.getDate().orElse(null), item.getDescription().orElse(null)));
      }

      return history;
   }

   private TranslationState translateToPluginConfigurations(MedusaConfiguration config) {
      TranslationState translation = new TranslationState();
      config.visitAllAndReport(configElement -> {
         for (Plugin plugin : configElement.allPluginsInArbitraryOrder()) {
            Optional<MutablePluginConfiguration> pluginConfig = translate(plugin);
            pluginConfig.ifPresent(mutablePluginConfiguration -> translation.addConfigTranslation(plugin, mutablePluginConfiguration));
         }
      });
      return translation;
   }

   private static Optional<MutablePluginConfiguration> translate(Plugin plugin) {
      if (plugin.getUuidRef().isPresent()) {
         return Optional.of(stubForUUID(plugin));
      }

      if (plugin.getType().isEmpty() && plugin.getName().isEmpty() && plugin.getTemplate().isEmpty()) {
         return Optional.empty();
      }

      MutablePluginConfiguration pluginConfiguration = new DefaultPluginConfiguration(plugin.getName().orElse(null));
      pluginConfiguration.setComment(plugin.getComment().orElse(null));
      pluginConfiguration.setPluginClassName(ClassNameNormalizer.normalizeClassName(plugin.getType().orElse(null)));
      pluginConfiguration.setUuid(plugin.getUuid().orElse(UUID.randomUUID().toString()));
      return Optional.of(pluginConfiguration);
   }

   private static MutablePluginConfiguration stubForUUID(Plugin plugin) throws PersistenceModelToPCModelTranslation.UUIDReferencePluginMustNotHaveAdditionalAttributes {
      assertAbsentFromUUIDRef(plugin, plugin.getType(), "type");
      assertAbsentFromUUIDRef(plugin, plugin.getName(), "name");
      assertAbsentFromUUIDRef(plugin, plugin.getTemplate(), "template");
      assertAbsentFromUUIDRef(plugin, plugin.getComment(), "comment");
      assertEmptyFromUUIDRef(plugin, plugin.getProperties(), "properties");
      return new DefaultPluginConfiguration(null);
   }

   private static void assertAbsentFromUUIDRef(Plugin plugin, Optional<String> type, String value) throws PersistenceModelToPCModelTranslation.UUIDReferencePluginMustNotHaveAdditionalAttributes {
      if (type.isPresent()) {
         throw new PersistenceModelToPCModelTranslation.UUIDReferencePluginMustNotHaveAdditionalAttributes(plugin.getUuidRef().get(), value);
      }
   }

   private static void assertEmptyFromUUIDRef(Plugin plugin, List<?> map, String value) throws PersistenceModelToPCModelTranslation.UUIDReferencePluginMustNotHaveAdditionalAttributes {
      if (!map.isEmpty()) {
         throw new PersistenceModelToPCModelTranslation.UUIDReferencePluginMustNotHaveAdditionalAttributes(plugin.getUuidRef().get(), value);
      }
   }

   private static void initializeTemplateRelationships(TranslationState pluginsByOrigin) throws PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      new PluginTemplateResolver(pluginsByOrigin).resolve();
   }

   private static void initializeUUIDRelationships(TranslationState pluginsByOrigin) throws PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      new PluginUUIDResolver(pluginsByOrigin).resolve();
   }

   private static void initializePropertyRelationships(TranslationState translation) {
      for (Plugin plugin : translation.allPlugins()) {
         MutablePluginConfiguration pluginConfiguration = translation.getPluginConfigurationFor(plugin).get();

         for (Property property : plugin.getProperties()) {
            PropertyConfiguration propertyConfiguration = toPropertyConfiguration(property, pluginConfiguration, translation);
            pluginConfiguration.setProperty(propertyConfiguration, toEnvId(property), toContext(property));
         }
      }
   }

   private static PropertyConfiguration toPropertyConfiguration(Property property, PluginConfiguration pluginConfiguration, TranslationState translation) {
      SinglePropertyToPropertyConfigurationTranslator visitor = new SinglePropertyToPropertyConfigurationTranslator(translation, pluginConfiguration);
      property.accept(visitor);
      return visitor.getCreated();
   }

   private static ConfigurationEnvironment toEnvId(Property property) {
      return ConfigurationEnvironment.configurationEnvironment(property.getEnvId().orElse(null));
   }

   private static ConfigurationContext toContext(Property property) {
      return ConfigurationContext.configurationContext(property.getContext().orElse(null));
   }

   private static List<MutablePluginConfiguration> topLevelPlugins(MedusaConfiguration config, TranslationState translation) {
      return config.getToplevelPlugins().stream().map(translation::getPluginConfigurationFor).filter(Optional::isPresent).map(Optional::get).toList();
   }

   private static boolean isDocumentTypeSnippet(MedusaConfiguration config) {
      return config.getConfigurationElement().isDocumentTypeSnippet();
   }

   private static ConfigSchemaVersion getConfigSchemaVersion(MedusaConfiguration config) {
      return config.getConfigurationElement().getSchemaVersion();
   }

   private static IamVersion getIamVersion(MedusaConfiguration config) throws PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      String versionString = toVersion(config.getConfigurationElement().getIamVersion());
      return IamVersion.fromString(versionString);
   }

   private static String toVersion(Optional<String> instanceVersion) throws PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationRuntimeException, PersistenceModelToPCModelTranslation.ConfigVersionIsNotWellformed {
      if (instanceVersion.isEmpty()) {
         throw new PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationRuntimeException(
            "Configuration has no version. Please first migrate to version \"" + OLDEST_VALID_VERSION + "\" or later."
         );
      } else if (!IamVersion.isValidVersion(instanceVersion.get())) {
         throw new PersistenceModelToPCModelTranslation.ConfigVersionIsNotWellformed(instanceVersion.get());
      } else if (IamVersion.fromString(instanceVersion.get()).compareTo(OLDEST_VALID_VERSION) >= 0) {
         return instanceVersion.get();
      } else {
         throw new PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationRuntimeException(
            "Your version: \"" + instanceVersion.get() + "\" is not supported. Please first migrate to version \"" + OLDEST_VALID_VERSION + "\" or later."
         );
      }
   }

   static final class ConfigVersionIsNotWellformed extends PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      ConfigVersionIsNotWellformed(String version) {
         super(String.format("The version %s is ill formed", version));
      }
   }

   static final class CyclicTemplateRelationshipDetected extends PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      CyclicTemplateRelationshipDetected(String templateName, Throwable cause) {
         super(
            String.format(
               "The plugin with the name %s is a cyclic relationship. Note that an instance cannot be the template of its template (also not transitively).",
               templateName
            ),
            cause
         );
      }
   }

   public static class PersistenceModelToPCModelTranslationException extends Exception {
      public PersistenceModelToPCModelTranslationException(String message, Throwable cause) {
         super(message, cause);
      }

      public PersistenceModelToPCModelTranslationException(String message) {
         super(message);
      }
   }

   public static class PersistenceModelToPCModelTranslationRuntimeException extends RuntimeException {
      public PersistenceModelToPCModelTranslationRuntimeException(String message) {
         super(message);
      }
   }

   static final class PluginReferenceCannotBeFollowed extends PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationException {
      PluginReferenceCannotBeFollowed(String templateName) {
         super(String.format("A plugin with name %s does not exist but is referenced.", templateName));
      }
   }

   static final class UUIDReferencePluginMustNotHaveAdditionalAttributes
      extends PersistenceModelToPCModelTranslation.PersistenceModelToPCModelTranslationRuntimeException {
      UUIDReferencePluginMustNotHaveAdditionalAttributes(String uuid, String value) {
         super(String.format("A plugin that references %s has a disallowed value %s.", uuid, value));
      }
   }
}
