package com.airlock.iam.common.api.domain.model.role;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

public final class Role {
   private static final ELogger LOG = ELoggerFactory.getLogger(Role.class);
   private static final String TIMEOUT_SEPARATOR = ":";
   private static final String AIRLOCK_ENV_COOKIE_EMPTY_ROLE = "none";
   private static final Pattern ROLE_WITH_IDLE_TIMEOUT_PATTERN = Pattern.compile("^(.+):(-?\\d+)$");
   private static final Pattern ROLE_WITH_IDLE_TIMEOUT_AND_LIFETIME_PATTERN = Pattern.compile("^(.+):(-?\\d+):(-?\\d+)$");
   private static final Pattern CONTROL_CHARS_PATTERN = Pattern.compile("\\p{C}");
   private final String roleName;
   private final Optional<Integer> idleTimeout;
   private final Optional<Integer> lifetime;

   private Role(String roleName, Optional<Integer> idleTimeout, Optional<Integer> lifetime) {
      validateRoleName(roleName);
      Objects.requireNonNull(idleTimeout);
      Objects.requireNonNull(lifetime);
      this.roleName = roleName;
      this.idleTimeout = idleTimeout;
      this.lifetime = lifetime;
   }

   public static Role parseRole(@NonNull String roleString) {
      if (roleString == null) {
         throw new NullPointerException("roleString is marked non-null but is null");
      }

      validateRoleName(roleString);
      String role = roleString.trim();
      Matcher roleMatcher = ROLE_WITH_IDLE_TIMEOUT_AND_LIFETIME_PATTERN.matcher(role);
      if (roleMatcher.matches()) {
         return parseRoleWithIdleTimeoutAndLifetime(roleMatcher);
      }

      roleMatcher = ROLE_WITH_IDLE_TIMEOUT_PATTERN.matcher(role);
      return roleMatcher.matches() ? parseRoleWithIdleTimeout(roleMatcher) : new Role(role, Optional.empty(), Optional.empty());
   }

   public static Role roleForName(String roleName) {
      return new Role(roleName, Optional.empty(), Optional.empty());
   }

   public static void validateRoleName(String roleName) {
      if (StringUtils.isBlank(roleName)) {
         throw new FailureException("Role name must not be blank.");
      }

      if (roleName.equals("none")) {
         throw new FailureException("The role name \"none\" is a reserved role name.");
      }

      Matcher controlCharsMatcher = CONTROL_CHARS_PATTERN.matcher(roleName);
      if (controlCharsMatcher.find()) {
         throw new FailureException("The role name must not contain control characters: \"" + controlCharsMatcher.replaceAll("?") + "\"");
      }
   }

   public Role withoutTimeouts() {
      return new Role(this.getRoleName(), Optional.empty(), Optional.empty());
   }

   public Role withIdleTimeout(int newIdleTimeout) {
      return new Role(this.getRoleName(), Optional.of(newIdleTimeout), this.getLifetime());
   }

   public Role withLifetime(int newLifetime) {
      return new Role(this.getRoleName(), this.getIdleTimeout(), Optional.of(newLifetime));
   }

   public boolean hasSameName(Role otherRole) {
      if (!this.getRoleName().equals(otherRole.getRoleName()) && this.getRoleName().equalsIgnoreCase(otherRole.getRoleName())) {
         LOG.debug("Equality-check of role names that differ only in case: \"" + this.getRoleName() + "\" vs \"" + otherRole.getRoleName() + "\"");
      }

      return this.getRoleName().equals(otherRole.getRoleName());
   }

   public String formatRole() {
      return this.roleName
         + this.idleTimeout.<String>map(t -> ":" + t).orElse(this.lifetime.isPresent() ? ":0" : "")
         + this.lifetime.<String>map(t -> ":" + t).orElse("");
   }

   public boolean hasTimeOut() {
      return this.idleTimeout.isPresent() || this.lifetime.isPresent();
   }

   @Override
   public String toString() {
      return this.formatRole();
   }

   private static Role parseRoleWithIdleTimeoutAndLifetime(Matcher roleMatcher) {
      String roleName = roleMatcher.group(1);
      Optional<Integer> idleTimeout = parseNonNegativeInteger(roleMatcher.group(2));
      Optional<Integer> lifetime = parseNonNegativeInteger(roleMatcher.group(3));
      return !idleTimeout.isEmpty() && !lifetime.isEmpty()
         ? new Role(roleName, idleTimeout.filter(isPositive()), lifetime.filter(isPositive()))
         : new Role(roleMatcher.group(0), Optional.empty(), Optional.empty());
   }

   private static Role parseRoleWithIdleTimeout(Matcher roleMatcher) {
      String roleName = roleMatcher.group(1);
      Optional<Integer> idleTimeout = parseNonNegativeInteger(roleMatcher.group(2));
      return idleTimeout.isEmpty()
         ? new Role(roleMatcher.group(0), Optional.empty(), Optional.empty())
         : new Role(roleName, idleTimeout.filter(isPositive()), Optional.empty());
   }

   private static Optional<Integer> parseNonNegativeInteger(String intString) {
      try {
         int value = Integer.parseInt(intString);
         return Optional.of(value).filter(t -> t >= 0);
      } catch (NumberFormatException e) {
         return Optional.empty();
      }
   }

   private static Predicate<Integer> isPositive() {
      return t -> t > 0;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Integer> getIdleTimeout() {
      return this.idleTimeout;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Integer> getLifetime() {
      return this.lifetime;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof Role other)) {
         return false;
      } else {
         Object this$roleName = this.roleName;
         Object other$roleName = other.roleName;
         if (this$roleName == null ? other$roleName == null : this$roleName.equals(other$roleName)) {
            Object this$idleTimeout = this.idleTimeout;
            Object other$idleTimeout = other.idleTimeout;
            if (this$idleTimeout == null ? other$idleTimeout == null : this$idleTimeout.equals(other$idleTimeout)) {
               Object this$lifetime = this.lifetime;
               Object other$lifetime = other.lifetime;
               return this$lifetime == null ? other$lifetime == null : this$lifetime.equals(other$lifetime);
            } else {
               return false;
            }
         } else {
            return false;
         }
      }
   }

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