package com.airlock.iam.admin.application.configuration.token;

import com.airlock.iam.admin.domain.model.token.TokenController;
import com.airlock.iam.base.infrastructure.reflection.ReflectionHelper;
import com.airlock.iam.core.misc.util.locale.LocaleUtil;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public final class TokenControllerIds {
   private TokenControllerIds() {
   }

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

   public Optional<String> getFirstConflictingId(List<PluginConfiguration> tokenControllers, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      Set<String> ids = new HashSet<>();

      for (PluginConfiguration tokenController : tokenControllers) {
         Optional<String> id = this.getId(tokenController, env, context);
         if (id.isPresent()) {
            if (ids.contains(id.get().toLowerCase(LocaleUtil.getForUserStrings()))) {
               return id;
            }

            ids.add(id.get().toLowerCase(LocaleUtil.getForUserStrings()));
         }
      }

      return Optional.empty();
   }

   private Optional<String> getId(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      Class<?> controllerType = this.getPluginClass(config, env, context);
      Optional<Field> idField = this.getIdField(controllerType, config, env, context);
      if (idField.isEmpty()) {
         if (!this.isTokenController(controllerType)) {
            return Optional.empty();
         } else {
            throw new ConfigurationException(
               config,
               env,
               context,
               String.format(
                  "No field annotated with @%s found for token controller %s",
                  TokenController.TokenControllerId.class.getSimpleName(),
                  controllerType.getName()
               )
            );
         }
      } else {
         String id = this.getId(config, idField.get(), env, context);
         return Optional.ofNullable(id);
      }
   }

   private Class<?> getPluginClass(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      try {
         return config.getPluginClass();
      } catch (InitializeException e) {
         throw new ConfigurationException(config, env, context, String.format("Failed to load class %s", config.getPluginClassName()));
      }
   }

   private boolean isTokenController(Class<?> cls) {
      return TokenController.class.isAssignableFrom(cls);
   }

   private Optional<Field> getIdField(Class<?> cls, PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws ConfigurationException {
      Set<Field> fields = ReflectionHelper.getAllFieldsWithAnnotations(List.of(TokenController.TokenControllerId.class), cls);
      if (fields.size() > 1) {
         throw new ConfigurationException(
            config,
            env,
            context,
            String.format(
               "Found %d fields annotated with @%s instead of one in token controller %s",
               fields.size(),
               TokenController.TokenControllerId.class.getSimpleName(),
               cls.getName()
            )
         );
      } else {
         return fields.size() == 1 ? Optional.of(fields.iterator().next()) : Optional.empty();
      }
   }

   private String getId(PluginConfiguration config, Field field, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      return this.isStatic(field)
         ? ReflectionHelper.getStringValue(field, null, null)
         : StringPropertyValueReader.effectiveStringConfigValue(config, field.getName(), env, context);
   }

   private boolean isStatic(Field field) {
      return Modifier.isStatic(field.getModifiers());
   }
}
