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

import com.airlock.iam.admin.domain.model.administrator.permissions.Actions;
import com.airlock.iam.base.infrastructure.reflection.ReflectionHelper;
import com.airlock.iam.common.application.configuration.authorization.ResourceAccessControllerAccessRule;
import com.google.common.collect.Lists;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

final class ActionsToRest {
   private final RoleBasedAccessControllerConfig config;
   private final boolean recoveryMode;

   Map<String, List<ResourceAccessControllerAccessRule>> getMappings() {
      List<ActionsToRest.RestAction> restActions = Lists.newArrayList(
         new ActionsToRest.RestAction[]{
            RestActionDefinitions.listUsers(),
            RestActionDefinitions.viewUser(),
            RestActionDefinitions.viewContextData(),
            RestActionDefinitions.editUser(),
            RestActionDefinitions.editUsername(),
            RestActionDefinitions.deleteUser(),
            RestActionDefinitions.createUser(),
            RestActionDefinitions.lockUser(),
            RestActionDefinitions.unlockUser(),
            RestActionDefinitions.viewUserTrailLog(),
            RestActionDefinitions.orderPassword(),
            RestActionDefinitions.unorderPassword(),
            RestActionDefinitions.deletePassword(),
            RestActionDefinitions.generatePassword(),
            RestActionDefinitions.triggerPasswordReset(),
            RestActionDefinitions.changeAdministratorPassword(),
            RestActionDefinitions.viewToken(),
            RestActionDefinitions.editToken(),
            RestActionDefinitions.manageToken(),
            RestActionDefinitions.orderNewToken(),
            RestActionDefinitions.unorderNewToken(),
            RestActionDefinitions.deleteToken(),
            RestActionDefinitions.activateToken(),
            RestActionDefinitions.deactivateToken(),
            RestActionDefinitions.generateTokenIak(),
            RestActionDefinitions.displayUserManagementExtensions(),
            RestActionDefinitions.manageOAuth2Consents(),
            RestActionDefinitions.listMaintenanceMessages(),
            RestActionDefinitions.editMaintenanceMessage(),
            RestActionDefinitions.deleteMaintenanceMessage(),
            RestActionDefinitions.viewLicense(),
            RestActionDefinitions.viewLog(),
            RestActionDefinitions.listAdministrators(),
            RestActionDefinitions.viewAdministrators(),
            RestActionDefinitions.createAdministrator(),
            RestActionDefinitions.deleteAdministrator(),
            RestActionDefinitions.editAdministrator(),
            RestActionDefinitions.lockAdministrator(),
            RestActionDefinitions.unlockAdministrator(),
            RestActionDefinitions.generateAdminPassword(),
            RestActionDefinitions.deleteAdminPassword(),
            RestActionDefinitions.viewTechnicalClients(),
            RestActionDefinitions.createEditTechnicalClient(),
            RestActionDefinitions.deleteTechnicalClient(),
            RestActionDefinitions.lockTechnicalClient(),
            RestActionDefinitions.unlockTechnicalClient()
         }
      );
      return this.convertToMappings(restActions);
   }

   private Map<String, List<ResourceAccessControllerAccessRule>> convertToMappings(List<ActionsToRest.RestAction> restActions) {
      return restActions.stream()
         .filter(restAction -> this.getAllowedActions().contains(restAction.action))
         .collect(
            Collectors.toMap(
               ActionsToRest.RestAction::name,
               restAction -> restAction.rules
                  .stream()
                  .map(rule -> this.allow(restAction.action, rule.getMethod(), rule.getPattern()))
                  .collect(Collectors.toList())
            )
         );
   }

   private Set<Actions> getAllowedActions() {
      return new HashSet<>(Actions.allowedActions(this.recoveryMode));
   }

   private ResourceAccessControllerAccessRule allow(Actions action, String methods, String pattern) {
      return ResourceAccessControllerAccessRule.allowing(Pattern.compile(pattern), methods, this.getRoles(action));
   }

   private String getRoles(Actions action) {
      Field field = (Field)ReflectionHelper.getField(action.name(), this.config.getClass()).get();
      return ReflectionHelper.getStringValue(field, this.config, "");
   }

   @SuppressFBWarnings(justification = "generated code")
   public ActionsToRest(RoleBasedAccessControllerConfig config, boolean recoveryMode) {
      this.config = config;
      this.recoveryMode = recoveryMode;
   }

   static class RestAction {
      private List<ActionsToRest.Rule> rules;
      private Actions action;

      String name() {
         return this.action.name();
      }

      @SuppressFBWarnings(justification = "generated code")
      RestAction(List<ActionsToRest.Rule> rules, Actions action) {
         this.rules = rules;
         this.action = action;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static ActionsToRest.RestAction.RestActionBuilder builder() {
         return new ActionsToRest.RestAction.RestActionBuilder();
      }

      @SuppressFBWarnings(justification = "generated code")
      public List<ActionsToRest.Rule> getRules() {
         return this.rules;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Actions getAction() {
         return this.action;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class RestActionBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private ArrayList<ActionsToRest.Rule> rules;
         @SuppressFBWarnings(justification = "generated code")
         private Actions action;

         @SuppressFBWarnings(justification = "generated code")
         RestActionBuilder() {
         }

         @SuppressFBWarnings(justification = "generated code")
         public ActionsToRest.RestAction.RestActionBuilder rule(ActionsToRest.Rule rule) {
            if (this.rules == null) {
               this.rules = new ArrayList<>();
            }

            this.rules.add(rule);
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public ActionsToRest.RestAction.RestActionBuilder rules(Collection<? extends ActionsToRest.Rule> rules) {
            if (rules == null) {
               throw new NullPointerException("rules cannot be null");
            }

            if (this.rules == null) {
               this.rules = new ArrayList<>();
            }

            this.rules.addAll(rules);
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public ActionsToRest.RestAction.RestActionBuilder clearRules() {
            if (this.rules != null) {
               this.rules.clear();
            }

            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public ActionsToRest.RestAction.RestActionBuilder action(Actions action) {
            this.action = action;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public ActionsToRest.RestAction build() {
            return new ActionsToRest.RestAction(switch (this.rules == null ? 0 : this.rules.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.rules.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.rules));
            }, this.action);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "ActionsToRest.RestAction.RestActionBuilder(rules=" + this.rules + ", action=" + this.action + ")";
         }
      }
   }

   static class Rule {
      private String method;
      private String pattern;

      static ActionsToRest.Rule of(String method, String pattern) {
         return new ActionsToRest.Rule(method, pattern);
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getMethod() {
         return this.method;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getPattern() {
         return this.pattern;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof ActionsToRest.Rule other)) {
            return false;
         } else if (!other.canEqual(this)) {
            return false;
         } else {
            Object this$method = this.method;
            Object other$method = other.method;
            if (this$method == null ? other$method == null : this$method.equals(other$method)) {
               Object this$pattern = this.pattern;
               Object other$pattern = other.pattern;
               return this$pattern == null ? other$pattern == null : this$pattern.equals(other$pattern);
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      protected boolean canEqual(Object other) {
         return other instanceof ActionsToRest.Rule;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $method = this.method;
         result = result * 59 + ($method == null ? 43 : $method.hashCode());
         Object $pattern = this.pattern;
         return result * 59 + ($pattern == null ? 43 : $pattern.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      public Rule(String method, String pattern) {
         this.method = method;
         this.pattern = pattern;
      }
   }
}
