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

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.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.exception.PluginClassLoadingException;
import com.airlock.iam.plugin.api.util.PropertyAnnotationException;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.processor.FieldProcessors;
import com.airlock.iam.plugin.framework.misc.processor.PropertyProcessor;
import com.airlock.iam.plugin.impl.cache.PluginClassCache;
import com.google.common.collect.Lists;
import jakarta.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class ConfigurablePropertyFactory {
   private static final ELogger LOG = ELoggerFactory.getLogger(ConfigurablePropertyFactory.class);

   private ConfigurablePropertyFactory() {
   }

   public static ConfigurablePropertyFactory getInstance() {
      return new ConfigurablePropertyFactory();
   }

   public List<ConfigurableProperty> createProperties(
      MutablePluginConfiguration pluginConfiguration, ConfigurationEnvironment env, ConfigurationContext context
   ) throws PluginClassLoadingException {
      Class<?> pluginClass = this.getPluginClass(pluginConfiguration);
      Collection<Field> pluginFields = this.getPluginFields(pluginClass);
      return this.getConfigurableProperties(pluginFields, pluginConfiguration, env, context);
   }

   @Nullable
   private Class<?> getPluginClass(MutablePluginConfiguration pluginConfiguration) {
      Class<?> pluginClass;
      try {
         pluginClass = pluginConfiguration.getPluginClass();
      } catch (InitializeException e) {
         LOG.error(LogTypes.CONFIGURATION_ERROR, "Initialize Exception: " + e.getMessage());
         return null;
      }

      if (pluginClass == null) {
         LOG.error(LogTypes.CONFIGURATION_ERROR, "Could not load plugin class " + pluginConfiguration.getPluginClassName());
      }

      return pluginClass;
   }

   Collection<Field> getPluginFields(@Nullable Class<?> pluginClass) throws PluginClassLoadingException {
      return pluginClass == null ? Collections.emptyList() : PluginClassCache.collectAllFields(pluginClass, true);
   }

   private List<ConfigurableProperty> getConfigurableProperties(
      Collection<Field> pluginFields, MutablePluginConfiguration pluginConfiguration, ConfigurationEnvironment env, ConfigurationContext context
   ) {
      List<ConfigurableProperty> configurableProperties = Lists.newLinkedList();

      for (Field field : pluginFields) {
         ConfigurableProperty configurableProperty = this.getConfigurableProperty(field, pluginConfiguration, env, context);
         if (configurableProperty != null) {
            configurableProperties.add(configurableProperty);
         }
      }

      return configurableProperties;
   }

   @Nullable
   private ConfigurableProperty getConfigurableProperty(
      Field pluginField, MutablePluginConfiguration pluginConfiguration, ConfigurationEnvironment env, ConfigurationContext context
   ) {
      ConfigurableProperty configurableProperty = null;

      try {
         PropertyProcessor<?> propertyProcessor = FieldProcessors.getPropertyProcessorForField(pluginField);
         if (propertyProcessor != null) {
            configurableProperty = this.createConfigurableProperty(pluginField, propertyProcessor, pluginConfiguration, env, context);
         }
      } catch (PropertyAnnotationException e) {
         LOG.error(LogTypes.CONFIGURATION_ERROR, "PropertyAnnotation Exception: " + e.getMessage(), e);
      }

      return configurableProperty;
   }

   ConfigurableProperty createConfigurableProperty(
      Field pluginField,
      PropertyProcessor<?> propertyProcessor,
      MutablePluginConfiguration pluginConfiguration,
      ConfigurationEnvironment env,
      ConfigurationContext context
   ) {
      PropertyAdapter<Annotation> propertyAdapter = this.createPropertyAdapter(pluginField, propertyProcessor, pluginConfiguration, env, context);
      return new ConfigurableProperty(propertyAdapter);
   }

   private PropertyAdapter<Annotation> createPropertyAdapter(
      Field pluginField,
      PropertyProcessor<?> propertyProcessor,
      MutablePluginConfiguration pluginConfiguration,
      ConfigurationEnvironment env,
      ConfigurationContext context
   ) {
      return new PropertyAdapter<>(pluginField, propertyProcessor, pluginConfiguration, env, context);
   }
}
