package com.airlock.iam.authentication.api.domain.service.username;

import com.airlock.iam.common.api.domain.model.locking.LockReason;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import lombok.NonNull;

public final class IdentificationResult {
   public static final String LOCK_REASON_ALLOWS_SELF_UNLOCK_ATTR = "lockReasonAllowsSelfUnlock";
   private final Optional<UserIdentity> userIdentity;
   private final Optional<IdentificationResult.IdentificationFailure> failure;

   private static IdentificationResult failure(IdentificationResult.IdentificationFailure failureResult) {
      return new IdentificationResult(Optional.empty(), Optional.of(failureResult));
   }

   private static IdentificationResult failure(IdentificationResult.IdentificationFailure failureResult, UserIdentity userIdentity) {
      return new IdentificationResult(Optional.of(userIdentity), Optional.of(failureResult));
   }

   public static IdentificationResult identified(UserIdentity userIdentity) {
      return new IdentificationResult(Optional.of(userIdentity), Optional.empty());
   }

   public boolean isFailure() {
      return this.failure.isPresent();
   }

   public boolean hasError(IdentificationResult.IdentificationFailure.IdentificationError expectedError) {
      return this.failure.isPresent() && this.failure.get().error().equals(expectedError);
   }

   public boolean hasErrorIn(IdentificationResult.IdentificationFailure.IdentificationError... expectedErrors) {
      return Stream.of(expectedErrors).anyMatch(this::hasError);
   }

   public static IdentificationResult userNotFound(UserIdentity identity) {
      return failure(new IdentificationResult.IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError.USER_NOT_FOUND), identity);
   }

   public static IdentificationResult usernameInvalid() {
      return failure(
         IdentificationResult.IdentificationFailure.identificationFailureWithRetry(
            IdentificationResult.IdentificationFailure.IdentificationError.USERNAME_INVALID
         )
      );
   }

   public static IdentificationResult differentUsername() {
      return failure(new IdentificationResult.IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError.DIFFERENT_USERNAME));
   }

   public static IdentificationResult concurrentAccess() {
      return failure(
         IdentificationResult.IdentificationFailure.identificationFailureWithRetry(
            IdentificationResult.IdentificationFailure.IdentificationError.CONCURRENT_ACCESS
         )
      );
   }

   public static IdentificationResult userInvalid(UserIdentity identity) {
      return failure(new IdentificationResult.IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError.USER_INVALID), identity);
   }

   public static IdentificationResult userTempLocked(UserIdentity identity) {
      return failure(new IdentificationResult.IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError.USER_TEMP_LOCKED), identity);
   }

   public static IdentificationResult userLocked(UserIdentity identity, LockReason lockReason) {
      Map<String, Object> additionalAttributes = Map.of("lockReasonAllowsSelfUnlock", lockReason.isSelfUnlockSupported());
      return failure(
         new IdentificationResult.IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError.USER_LOCKED, false, additionalAttributes),
         identity
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   public IdentificationResult(Optional<UserIdentity> userIdentity, Optional<IdentificationResult.IdentificationFailure> failure) {
      this.userIdentity = userIdentity;
      this.failure = failure;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<UserIdentity> getUserIdentity() {
      return this.userIdentity;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<IdentificationResult.IdentificationFailure> getFailure() {
      return this.failure;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public String toString() {
      return "IdentificationResult(userIdentity=" + this.getUserIdentity() + ", failure=" + this.getFailure() + ")";
   }

   public record IdentificationFailure(
      @NonNull IdentificationResult.IdentificationFailure.IdentificationError error, boolean retryAllowed, @NonNull Map<String, Object> additionalAttributes
   ) {
      @SuppressFBWarnings(justification = "generated code")
      public IdentificationFailure {
         if (error == null) {
            throw new NullPointerException("error is marked non-null but is null");
         }

         if (additionalAttributes == null) {
            throw new NullPointerException("additionalAttributes is marked non-null but is null");
         }
      }

      private IdentificationFailure(IdentificationResult.IdentificationFailure.IdentificationError error) {
         this(error, false, Collections.emptyMap());
      }

      static IdentificationResult.IdentificationFailure identificationFailureWithRetry(IdentificationResult.IdentificationFailure.IdentificationError error) {
         return new IdentificationResult.IdentificationFailure(error, true, Collections.emptyMap());
      }

      public enum IdentificationError {
         USERNAME_INVALID,
         USER_NOT_FOUND,
         DIFFERENT_USERNAME,
         USER_LOCKED,
         USER_INVALID,
         USER_TEMP_LOCKED,
         CONCURRENT_ACCESS;
      }
   }
}
