package com.airlock.iam.plugin.configeditor.util;

import com.airlock.iam.plugin.api.annotation.config.DeprecatedPlugin;
import com.airlock.iam.plugin.api.annotation.config.NextActions;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.exception.PluginClassLoadingException;
import com.airlock.iam.plugin.api.util.PropertyAnnotationException;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.framework.infrastructure.io.YamlConfigWriter;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.airlock.iam.plugin.framework.misc.processor.BooleanPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.DoublePropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.EnumPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.FieldProcessors;
import com.airlock.iam.plugin.framework.misc.processor.FilePropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.IntegerPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.LongPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.PropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.RegexListPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.RegexPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.StringListPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.processor.StringPropertyProcessor;
import com.airlock.iam.plugin.framework.misc.shortnames.ShortNameMapping;
import com.airlock.iam.plugin.framework.misc.shortnames.ShortNameMappingFactory;
import com.airlock.iam.plugin.framework.misc.ui.DeprecatedPluginHelper;
import com.airlock.iam.plugin.impl.cache.PluginClassCache;
import com.google.common.base.Joiner;
import com.vaadin.ui.UI;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import org.snakeyaml.engine.v2.api.Dump;

public final class PluginDocumentation {
   private static final Pattern LINE_START_PATTERN = Pattern.compile("(?m)^");
   private static final ShortNameMapping SHORT_NAME_MAPPING = ShortNameMappingFactory.INSTANCE.getCurrentMapping();

   private PluginDocumentation() {
   }

   @SuppressFBWarnings("POTENTIAL_XML_INJECTION")
   public static String buildDocumentation(Class<?> pluginClass) {
      if (pluginClass == null) {
         return "";
      }

      StringBuilder info = new StringBuilder();
      Plugin pluginAnnotation = pluginClass.getAnnotation(Plugin.class);
      if (pluginAnnotation != null) {
         if (pluginClass.isAnnotationPresent(DeprecatedPlugin.class)) {
            info.append("<h3>Deprecated</h3>").append(DeprecatedPluginHelper.getDescriptionHtmlMarkup(pluginClass.getAnnotation(DeprecatedPlugin.class)));
         }

         info.append("<h3>Plugin</h3>").append(PluginLoader.getDisplayName(pluginClass)).append("<h3>Description</h3>").append(pluginAnnotation.description());
      }

      NextActions nextActionsAnnotation = pluginClass.getAnnotation(NextActions.class);
      if (nextActionsAnnotation != null && nextActionsAnnotation.value().length > 0) {
         info.append("<h3>List of next step codes that can be returned by this step:</h3>");
         info.append("<ul>");

         for (Class<?> possibleNextAction : nextActionsAnnotation.value()) {
            for (Enum<?> action : (Enum[])possibleNextAction.getEnumConstants()) {
               info.append("<li><tt>");
               info.append(action.name());
               info.append("</tt></li>");
            }
         }

         info.append("</ul>");
      }

      Set<String> requiredLicenseTags = app().getLicenseManager().getRequiredTagsForPluginClass(pluginClass.getName());
      if (!requiredLicenseTags.isEmpty()) {
         info.append("<h3>Requires one of the following license tags</h3>").append(Joiner.on(", ").join(requiredLicenseTags));
      }

      info.append("<h3>Class name</h3>").append(pluginClass.getName());
      Optional<String> shortName = SHORT_NAME_MAPPING.retrieveShortNameForType(pluginClass.getName());
      info.append("<h3>YAML template</h3>")
         .append("<i>The following template shows how this plugin can be configured in YAML. Default values are set where available.</i>")
         .append("<pre>")
         .append("type: " + shortName.orElse(pluginClass.getName()) + "\n")
         .append("id: " + shortName.orElse(pluginClass.getName().substring(pluginClass.getName().lastIndexOf(46) + 1)) + "-xxxxxx\n")
         .append("displayName: \n")
         .append("comment: \n");

      try {
         Collection<Field> fields = PluginClassCache.collectAllFields(pluginClass, true);
         List<Field> properties = fields.stream().filter(field -> getProcessorForProperty(field) != null).toList();
         if (!properties.isEmpty()) {
            Dump yamlDumper = new Dump(YamlConfigWriter.dumpSettings());
            info.append("properties:\n");
            properties.forEach(field -> info.append(indent("  ", getYamlProperty(field, Objects.requireNonNull(getProcessorForProperty(field)), yamlDumper))));
         }
      } catch (PluginClassLoadingException var12) {
      }

      info.append("</pre>");
      return info.toString();
   }

   private static String indent(String spaces, String string) {
      return LINE_START_PATTERN.matcher(string).replaceAll(spaces);
   }

   private static PropertyProcessor<?> getProcessorForProperty(Field field) {
      try {
         return FieldProcessors.getPropertyProcessorForField(field);
      } catch (PropertyAnnotationException e) {
         return null;
      }
   }

   private static String getYamlProperty(Field field, PropertyProcessor<?> processor, Dump yamlDumper) {
      if (processor.hasDefaultValue(field)) {
         if (processor instanceof BooleanPropertyProcessor
            || processor instanceof IntegerPropertyProcessor
            || processor instanceof DoublePropertyProcessor
            || processor instanceof LongPropertyProcessor
            || processor instanceof StringPropertyProcessor
            || processor instanceof EnumPropertyProcessor
            || processor instanceof FilePropertyProcessor
            || processor instanceof RegexPropertyProcessor) {
            return yamlDumper.dumpToString(Map.of(field.getName(), Objects.toString(processor.getDefaultValue(field))));
         }

         if (processor instanceof StringListPropertyProcessor || processor instanceof RegexListPropertyProcessor) {
            return yamlDumper.dumpToString(Map.of(field.getName(), List.of(Map.of("valueList", Objects.requireNonNull(processor.getDefaultValue(field))))));
         }
      }

      return field.getName() + ": \n";
   }

   private static ConfigEditorApplication app() {
      return (ConfigEditorApplication)UI.getCurrent();
   }
}
