package com.airlock.iam.factor.domain.model.airlock2fa;

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import lombok.NonNull;

public final class Airlock2FAAuthDevice {
   @NonNull
   private final Airlock2FADeviceId id;
   @NonNull
   private final Airlock2FADeviceType deviceType;
   @NonNull
   private final Airlock2FADeviceDisplayName displayName;
   @NonNull
   private final Set<Airlock2FADeviceCapability> capabilities;
   @NonNull
   private final Instant enrolledAt;
   @NonNull
   private final Optional<Airlock2FADeviceId> migratedFrom;

   public Airlock2FAAuthDevice(
      @NonNull Airlock2FADeviceId id,
      @NonNull Airlock2FADeviceType deviceType,
      @NonNull Airlock2FADeviceDisplayName displayName,
      @NonNull Set<Airlock2FADeviceCapability> capabilities,
      @NonNull Instant enrolledAt,
      @NonNull Optional<Airlock2FADeviceId> migratedFrom
   ) {
      if (id == null) {
         throw new NullPointerException("id is marked non-null but is null");
      }

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

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

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

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

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

      this.id = id;
      this.deviceType = deviceType;
      this.displayName = displayName;
      this.capabilities = capabilities;
      this.enrolledAt = enrolledAt.truncatedTo(ChronoUnit.SECONDS);
      this.migratedFrom = migratedFrom;
   }

   public Airlock2FAFactorSet getSupportedFactors(Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      return Airlock2FAFactorSet.allAirlock2FAUserFactors().filter(factor -> this.isCapableOfFactor(factor, payloadEncryptionSettings));
   }

   public boolean isCapableOfAtLeastOneFactor(Airlock2FAFactorSet factors, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      return this.isCapableOfAtLeastOneFactor(factors.stream().toList(), payloadEncryptionSettings);
   }

   public boolean isCapableOfAtLeastOneFactor(Collection<Airlock2FAFactor> factors, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      return factors.stream().anyMatch(factor -> this.isCapableOfFactor(factor, payloadEncryptionSettings));
   }

   public boolean isCapableOfFactor(Airlock2FAFactor factor, Airlock2FAPayloadEncryptionSettings payloadEncryptionSettings) {
      if (factor.isOnline() && this.isHardwareToken()) {
         return false;
      } else {
         return factor == Airlock2FAFactor.OFFLINE_QR_CODE
               && this.isHardwareToken()
               && this.isCapableOfFactor(Airlock2FAFactor.OFFLINE_QR_CODE)
               && payloadEncryptionSettings.requireEncryptedPayloadForHwTokens()
            ? false
            : this.isCapableOfFactor(factor);
      }
   }

   public boolean isHardwareToken() {
      return this.deviceType == Airlock2FADeviceType.HARDWARE;
   }

   private boolean isCapableOfFactor(Airlock2FAFactor factor) {
      return this.capabilities.containsAll(Airlock2FADeviceCapability.requiredCapabilitiesForFactor(factor));
   }

   public Boolean isInCooldownPeriod(Duration cooldownPeriod) {
      Instant cooldownEndTime = this.enrolledAt.plus(cooldownPeriod);
      Instant currentTime = Instant.now();
      return currentTime.isBefore(cooldownEndTime) || currentTime.equals(cooldownEndTime);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder builder() {
      return new Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder();
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FADeviceId getId() {
      return this.id;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FADeviceType getDeviceType() {
      return this.deviceType;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Airlock2FADeviceDisplayName getDisplayName() {
      return this.displayName;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Set<Airlock2FADeviceCapability> getCapabilities() {
      return this.capabilities;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Instant getEnrolledAt() {
      return this.enrolledAt;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Optional<Airlock2FADeviceId> getMigratedFrom() {
      return this.migratedFrom;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof Airlock2FAAuthDevice other)) {
         return false;
      } else {
         Object this$id = this.id;
         Object other$id = other.id;
         if (this$id == null ? other$id == null : this$id.equals(other$id)) {
            Object this$deviceType = this.deviceType;
            Object other$deviceType = other.deviceType;
            if (this$deviceType == null ? other$deviceType == null : this$deviceType.equals(other$deviceType)) {
               Object this$displayName = this.displayName;
               Object other$displayName = other.displayName;
               if (this$displayName == null ? other$displayName == null : this$displayName.equals(other$displayName)) {
                  Object this$capabilities = this.capabilities;
                  Object other$capabilities = other.capabilities;
                  if (this$capabilities == null ? other$capabilities == null : this$capabilities.equals(other$capabilities)) {
                     Object this$enrolledAt = this.enrolledAt;
                     Object other$enrolledAt = other.enrolledAt;
                     if (this$enrolledAt == null ? other$enrolledAt == null : this$enrolledAt.equals(other$enrolledAt)) {
                        Object this$migratedFrom = this.migratedFrom;
                        Object other$migratedFrom = other.migratedFrom;
                        return this$migratedFrom == null ? other$migratedFrom == null : this$migratedFrom.equals(other$migratedFrom);
                     } else {
                        return false;
                     }
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         } else {
            return false;
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      Object $id = this.id;
      result = result * 59 + ($id == null ? 43 : $id.hashCode());
      Object $deviceType = this.deviceType;
      result = result * 59 + ($deviceType == null ? 43 : $deviceType.hashCode());
      Object $displayName = this.displayName;
      result = result * 59 + ($displayName == null ? 43 : $displayName.hashCode());
      Object $capabilities = this.capabilities;
      result = result * 59 + ($capabilities == null ? 43 : $capabilities.hashCode());
      Object $enrolledAt = this.enrolledAt;
      result = result * 59 + ($enrolledAt == null ? 43 : $enrolledAt.hashCode());
      Object $migratedFrom = this.migratedFrom;
      return result * 59 + ($migratedFrom == null ? 43 : $migratedFrom.hashCode());
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Airlock2FAAuthDeviceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Airlock2FADeviceId id;
      @SuppressFBWarnings(justification = "generated code")
      private Airlock2FADeviceType deviceType;
      @SuppressFBWarnings(justification = "generated code")
      private Airlock2FADeviceDisplayName displayName;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<Airlock2FADeviceCapability> capabilities;
      @SuppressFBWarnings(justification = "generated code")
      private Instant enrolledAt;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Airlock2FADeviceId> migratedFrom;

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder id(@NonNull Airlock2FADeviceId id) {
         if (id == null) {
            throw new NullPointerException("id is marked non-null but is null");
         }

         this.id = id;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder deviceType(@NonNull Airlock2FADeviceType deviceType) {
         if (deviceType == null) {
            throw new NullPointerException("deviceType is marked non-null but is null");
         }

         this.deviceType = deviceType;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder displayName(@NonNull Airlock2FADeviceDisplayName displayName) {
         if (displayName == null) {
            throw new NullPointerException("displayName is marked non-null but is null");
         }

         this.displayName = displayName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder capability(Airlock2FADeviceCapability capability) {
         if (this.capabilities == null) {
            this.capabilities = new ArrayList<>();
         }

         this.capabilities.add(capability);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder capabilities(Collection<? extends Airlock2FADeviceCapability> capabilities) {
         if (capabilities == null) {
            throw new NullPointerException("capabilities cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder clearCapabilities() {
         if (this.capabilities != null) {
            this.capabilities.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder enrolledAt(@NonNull Instant enrolledAt) {
         if (enrolledAt == null) {
            throw new NullPointerException("enrolledAt is marked non-null but is null");
         }

         this.enrolledAt = enrolledAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder migratedFrom(@NonNull Optional<Airlock2FADeviceId> migratedFrom) {
         if (migratedFrom == null) {
            throw new NullPointerException("migratedFrom is marked non-null but is null");
         }

         this.migratedFrom = migratedFrom;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Airlock2FAAuthDevice build() {
         return new Airlock2FAAuthDevice(
            this.id,
            this.deviceType,
            this.displayName,
            switch (this.capabilities == null ? 0 : this.capabilities.size()) {
               case 0 -> Collections.emptySet();
               case 1 -> Collections.singleton(this.capabilities.get(0));
               default -> {
                  Set<Airlock2FADeviceCapability> capabilities = new LinkedHashSet<>(
                     this.capabilities.size() < 1073741824 ? 1 + this.capabilities.size() + (this.capabilities.size() - 3) / 3 : Integer.MAX_VALUE
                  );
                  capabilities.addAll(this.capabilities);
                  yield Collections.unmodifiableSet(capabilities);
               }
            },
            this.enrolledAt,
            this.migratedFrom
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Airlock2FAAuthDevice.Airlock2FAAuthDeviceBuilder(id="
            + this.id
            + ", deviceType="
            + this.deviceType
            + ", displayName="
            + this.displayName
            + ", capabilities="
            + this.capabilities
            + ", enrolledAt="
            + this.enrolledAt
            + ", migratedFrom="
            + this.migratedFrom
            + ")";
      }
   }
}
