package com.airlock.iam.common.infrastructure;

import com.airlock.iam.common.infrastructure.factory.HelpdeskLoggerFactory;
import jakarta.inject.Named;
import jakarta.inject.Scope;
import jakarta.ws.rs.core.Configurable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.Rank;
import org.glassfish.hk2.utilities.Binder;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;

public class HK2Configurator {
   public HK2Configurator.HK2Binders registerHk2Binders(Configurable<?> configurableApplication) {
      HK2Configurator.Hk2BindersImpl binders = new HK2Configurator.Hk2BindersImpl();
      configurableApplication.register(binders.aggregatingBinder);
      binders.addFactoryBinderFor(HelpdeskLoggerFactory.class);
      return binders;
   }

   private static final class AggregatingBinder extends AbstractBinder {
      private final List<Binder> binders = new ArrayList<>();

      public void bind(DynamicConfiguration configuration) {
         super.bind(configuration);

         for (Binder binder : this.binders) {
            binder.bind(configuration);
         }
      }

      protected void configure() {
      }

      public void add(Binder binder) {
         this.binders.add(binder);
      }
   }

   private static final class FactoryBinder<F extends Factory<T>, T> extends AbstractBinder {
      private final Class<F> factoryClass;

      public FactoryBinder(Class<F> factoryClass) {
         this.factoryClass = factoryClass;
      }

      protected void configure() {
         Type type = extractFactoryTypeParameter(this.factoryClass);
         Method method = getProvideMethod(this.factoryClass);
         Named named = method.getAnnotation(Named.class);
         Rank rank = method.getAnnotation(Rank.class);
         Class<? extends Annotation> scope = getScopeAnnotation(method);
         Class<? extends Annotation> factoryScope = getScopeAnnotation(this.factoryClass);
         this.bindFactory(this.factoryClass, factoryScope)
            .to(type)
            .in(scope)
            .named(named != null ? named.value() : null)
            .ranked(rank != null ? rank.value() : 0);
      }

      private static Type extractFactoryTypeParameter(Class<?> factoryClass) {
         for (Type genericType : factoryClass.getGenericInterfaces()) {
            if (genericType instanceof ParameterizedType && Factory.class.equals(((ParameterizedType)genericType).getRawType())) {
               return ((ParameterizedType)genericType).getActualTypeArguments()[0];
            }
         }

         throw new IllegalStateException("No generic type found for " + factoryClass.getName());
      }

      private static Method getProvideMethod(Class<?> factoryType) {
         try {
            return factoryType.getMethod("provide");
         } catch (NoSuchMethodException e) {
            throw new IllegalStateException("No method with name 'provide' found in " + factoryType.getName());
         }
      }

      private static Class<? extends Annotation> getScopeAnnotation(Annotation[] annotations) {
         for (Annotation annotation : annotations) {
            if (annotation.annotationType().isAnnotationPresent(Scope.class)) {
               return annotation.annotationType();
            }
         }

         return null;
      }

      private static Class<? extends Annotation> getScopeAnnotation(Method method) {
         return method.isAnnotationPresent(RequestScoped.class) ? RequestScoped.class : getScopeAnnotation(method.getAnnotations());
      }

      private static Class<? extends Annotation> getScopeAnnotation(Class<?> cls) {
         return getScopeAnnotation(cls.getAnnotations());
      }
   }

   public interface HK2Binders {
      <T> void addSingletonBinderFor(T var1);

      void addBinder(Binder var1);

      <F extends Factory<T>, T> void addFactoryBinderFor(Class<F> var1);
   }

   private static final class Hk2BindersImpl implements HK2Configurator.HK2Binders {
      private final HK2Configurator.AggregatingBinder aggregatingBinder = new HK2Configurator.AggregatingBinder();

      @Override
      public void addBinder(Binder binder) {
         this.aggregatingBinder.add(binder);
      }

      @Override
      public <F extends Factory<T>, T> void addFactoryBinderFor(Class<F> factoryClass) {
         this.aggregatingBinder.add(new HK2Configurator.FactoryBinder(factoryClass));
      }

      @Override
      public <T> void addSingletonBinderFor(T instance) {
         this.aggregatingBinder.add(new HK2Configurator.SingletonBinder(instance));
      }
   }

   private static final class SingletonBinder<T> extends AbstractBinder {
      private final T singleton;

      public SingletonBinder(T singleton) {
         this.singleton = singleton;
      }

      protected void configure() {
         this.bind(this.singleton);
      }
   }
}
