package com.airlock.iam.common.infrastructure;

import com.airlock.iam.base.infrastructure.factory.guice.GuiceInjectorFactory;
import com.airlock.iam.base.infrastructure.injection.GuiceModules;
import com.airlock.iam.common.api.infrastructure.ApplicationInitializationHook;
import com.airlock.iam.common.infrastructure.event.EventSubscribers;
import com.airlock.iam.common.infrastructure.plugin.PluginRepository;
import com.google.inject.Injector;
import com.google.inject.Module;
import jakarta.inject.Inject;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.core.Configurable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.server.monitoring.ApplicationEvent;
import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.glassfish.jersey.server.monitoring.ApplicationEvent.Type;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;

public class GuiceConfigurator {
   private final RestApplicationConfig config;
   private final GuiceModules guiceModules;

   public GuiceConfigurator(RestApplicationConfig config) {
      this.config = config;
      this.guiceModules = new GuiceModules(config.applicationComponentProvider().guiceModules());
   }

   public void registerGuice(Configurable<?> configurableApplication, HK2Configurator.HK2Binders binders) {
      EventSubscribers eventSubscribers = this.collectEventSubscribers();
      PluginRepository pluginRepository = this.createPluginRepository();
      binders.addSingletonBinderFor(new GuiceConfigurator.InitializationHooks(this.config.applicationComponentProvider().initializationHooks()));
      binders.addSingletonBinderFor(this.guiceModules);
      binders.addSingletonBinderFor(eventSubscribers);
      binders.addSingletonBinderFor(pluginRepository);
      configurableApplication.register(GuiceConfigurator.JerseyEventListener.class);
   }

   private EventSubscribers collectEventSubscribers() {
      return new EventSubscribers(this.config.applicationComponentProvider().domainEventSubscribers());
   }

   private PluginRepository createPluginRepository() {
      Collection<Class<?>> pluginLogicTypes = this.config.applicationComponentProvider().pluginLogics();
      List<Class<? extends Module>> moduleTypes = this.guiceModules.getModulesTypesForPluginConfig();
      return new PluginRepository(pluginLogicTypes, moduleTypes);
   }

   private record InitializationHooks(Collection<Class<? extends ApplicationInitializationHook>> hooks) {
      public void forEach(Consumer<Class<? extends ApplicationInitializationHook>> consumer) {
         this.hooks.forEach(consumer);
      }
   }

   private static final class JerseyEventListener implements ApplicationEventListener {
      private final InjectionManager injectionManager;
      private final ServiceLocator serviceLocator;
      private final GuiceModules guiceModules;
      private final ServletContext servletContext;
      private final GuiceConfigurator.InitializationHooks initializationHooks;

      @Inject
      public JerseyEventListener(
         InjectionManager injectionManager,
         ServiceLocator serviceLocator,
         GuiceModules guiceModules,
         ServletContext servletContext,
         GuiceConfigurator.InitializationHooks initializationHooks
      ) {
         this.injectionManager = injectionManager;
         this.serviceLocator = serviceLocator;
         this.guiceModules = guiceModules;
         this.servletContext = servletContext;
         this.initializationHooks = initializationHooks;
      }

      public void onEvent(ApplicationEvent event) {
         if (event.getType() == Type.INITIALIZATION_APP_FINISHED) {
            List<Module> modules = new ArrayList<>(this.guiceModules.getGuiceModules());
            modules.add(new JerseyModule(this.injectionManager));
            Injector injector = GuiceInjectorFactory.factoryFromGlobalInjector(this.servletContext).createInjector(modules);
            GuiceBridge bridge = GuiceBridge.getGuiceBridge();
            bridge.initializeGuiceBridge(this.serviceLocator);
            GuiceIntoHK2Bridge guiceBridge = (GuiceIntoHK2Bridge)this.serviceLocator.getService(GuiceIntoHK2Bridge.class, new Annotation[0]);
            guiceBridge.bridgeGuiceInjector(injector);
            this.initializationHooks.forEach(initializer -> ((ApplicationInitializationHook)injector.getInstance(initializer)).onAppInitializationFinished());
         }
      }

      public RequestEventListener onRequest(RequestEvent requestEvent) {
         return null;
      }
   }
}
