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

import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.lifecycle.BeforePluginDeactivation;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.LogTypes;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
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.misc.config.ConfigurationProcessor;
import com.airlock.iam.plugin.framework.misc.config.ValidationEnvironment;
import com.airlock.iam.plugin.framework.misc.config.validation.PluginConfigurationValidator;
import com.airlock.iam.plugin.framework.misc.processor.FieldProcessors;
import com.airlock.iam.plugin.framework.misc.processor.PropertyProcessor;
import com.airlock.iam.plugin.impl.PluginDisplayNameReader;
import com.airlock.iam.plugin.impl.cache.PluginClassCache;
import com.airlock.iam.plugin.impl.cache.PluginClassCache.MethodOrder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.apache.commons.lang3.ClassUtils;

public final class PluginLoader {
   private static final ELogger LOG = ELoggerFactory.getLogger(PluginLoader.class);
   private static final Map<String, Result<Class<?>, InitializeException>> CLASS_CACHE = new ConcurrentHashMap<>();

   public static PluginLoader get() {
      return new PluginLoader();
   }

   private PluginLoader() {
   }

   public Object loadPlugin(PluginConfiguration pluginConfiguration) throws InitializeException {
      return this.loadPlugin(pluginConfiguration, ConfigurationEnvironment.common(), ConfigurationContext.defaultContext());
   }

   public Object loadPlugin(PluginConfiguration pluginConfiguration, ConfigurationEnvironment env, ConfigurationContext configurationContext) throws InitializeException {
      Objects.requireNonNull(pluginConfiguration, "Cannot load plugin with plugin configuration being null.");
      Objects.requireNonNull(configurationContext, "Cannot load plugin with configuration context being null.");
      PluginConfigurationValidator validator = new PluginConfigurationValidator(pluginConfiguration, env, configurationContext);
      ValidationEnvironment validationEnv = ValidationEnvironment.builder().environment(Environment.CURRENT_VM).rootDir(Path.of(".")).build();
      validator.validate(validationEnv);
      Class<?> pluginClass = pluginConfiguration.getPluginClass();
      if (!isMedusaPlugin(pluginClass)) {
         throw new InitializeException(
            "The type \"" + pluginClass.getName() + "\" is not an Airlock IAM plugin. It has no \"" + Plugin.class.getName() + "\" annotation."
         );
      }

      Object plugin = this.createInstance(pluginClass);
      ConfigurationProcessor cp = new ConfigurationProcessor(pluginConfiguration, env, configurationContext);
      cp.applyConfiguration(plugin, true);
      return plugin;
   }

   private static void callDeactivateLifecycleMethodOnUnloading(Object object) {
      if (object != null) {
         List<Method> methods;
         try {
            methods = PluginClassCache.collectAllAnnotatedMethods(object.getClass(), BeforePluginDeactivation.class, MethodOrder.SUBCLASS_FIRST);
         } catch (PluginClassLoadingException e) {
            LOG.warn(LogTypes.PLUGIN_INFO, "Error loading plugin class in order to invoke deactivation methods: " + object.getClass().getCanonicalName(), e);
            return;
         }

         for (Method m : methods) {
            try {
               m.invoke(object);
            } catch (Throwable e) {
               LOG.warn(
                  LogTypes.PLUGIN_INFO,
                  "Error invoking deactivation method \"" + object.getClass().getCanonicalName() + "." + m.getName() + "\": " + e.getMessage(),
                  e
               );
            }
         }
      }
   }

   public static void unloadPluginTree(Object plugin) {
      if (plugin != null) {
         try {
            for (Field field : PluginClassCache.collectAllFields(plugin.getClass())) {
               field.setAccessible(true);

               try {
                  PropertyProcessor<?> pp = FieldProcessors.getPropertyProcessorForField(field);
                  if (pp != null) {
                     for (Object subPlugin : pp.getPluginsFromField(plugin, field)) {
                        unloadPluginTree(subPlugin);
                     }
                  }
               } catch (PropertyAnnotationException var6) {
               }
            }
         } catch (PluginClassLoadingException var7) {
         }

         callDeactivateLifecycleMethodOnUnloading(plugin);
      }
   }

   private Object createInstance(Class<?> pluginClass) throws InitializeException {
      try {
         return pluginClass.getConstructor().newInstance();
      } catch (ReflectiveOperationException | ExceptionInInitializerError e) {
         throw new InitializeException("Cannot create instance of type \"" + pluginClass.getName() + "\": " + e.getMessage(), e);
      }
   }

   public static boolean isMedusaPlugin(Class<?> pluginClass) {
      return getPluginAnnotation(pluginClass) != null;
   }

   public static Class<?> loadClass(String className) throws InitializeException {
      return (Class<?>)CLASS_CACHE.computeIfAbsent(className, PluginLoader::loadClassResult).orElseThrow(Function.identity());
   }

   private static Result<Class<?>, InitializeException> loadClassResult(String className) {
      try {
         return Result.result(ClassUtils.getClass(className));
      } catch (ClassNotFoundException | LinkageError e) {
         return Result.failure(new PluginClassLoadingException(className, e));
      } catch (Exception e) {
         return Result.failure(new InitializeException("Class could not be loaded: " + className, e));
      }
   }

   private static Plugin getPluginAnnotation(Class<?> pluginClass) {
      Objects.requireNonNull(pluginClass, "pluginClass null");
      return pluginClass.getAnnotation(Plugin.class);
   }

   public static String getPluginDescription(Class<?> pluginClass) {
      Plugin pluginAnnotation = getPluginAnnotation(pluginClass);
      if (pluginAnnotation == null) {
         throw new IllegalArgumentException("Passed object is not an Airlock IAM plugin (missing " + Plugin.class.getName() + " annotation).");
      } else {
         return pluginAnnotation.description();
      }
   }

   public static String getDisplayName(Object plugin) {
      return getDisplayName(plugin.getClass());
   }

   public static String getDisplayName(Class<?> pluginClass) {
      return PluginDisplayNameReader.readDisplayName(pluginClass);
   }
}
