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

import com.airlock.iam.core.domain.model.token.TokenUsage;
import com.airlock.iam.factor.domain.model.fido.registration.FidoAttestationObject;
import com.airlock.iam.factor.domain.model.fido.registration.ResidentKeyRequirement;
import com.airlock.iam.factor.domain.model.fido.user.FidoUserHandle;
import com.airlock.iam.fido.api.domain.model.FidoCredential;
import com.airlock.iam.fido.api.domain.model.FidoCredentialId;
import com.airlock.iam.fido.api.domain.model.FidoPublicKeyCredentialId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Instant;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.NonNull;

public final class FidoRegistrationCredential implements FidoCredential {
   private static final Pattern VALID_TRANSPORT_PATTERN = Pattern.compile("[a-zA-Z-]+");
   @NonNull
   private final FidoCredentialId id;
   @NonNull
   private final FidoUserHandle userHandle;
   @NonNull
   private final FidoCredentialName name;
   @NonNull
   private final FidoCredentialDisplayName displayName;
   @NonNull
   private final FidoPublicKeyCredentialId publicKeyCredentialId;
   @NonNull
   private final FidoPublicKey publicKey;
   @NonNull
   private final FidoSignatureCounter signatureCounter;
   private final boolean residentRequested;
   @Nullable
   private final ResidentKeyRequirement residentKey;
   @NonNull
   private final FidoAttestationObject attestationObject;
   @NonNull
   private final Instant registeredAt;
   @NonNull
   private final TokenUsage usage;
   private final boolean locked;
   @NonNull
   private final Set<String> transports;

   public Optional<Instant> firstUsedAt() {
      return Optional.ofNullable(this.usage.getFirstUsageDate()).map(Date::toInstant);
   }

   public Optional<Instant> lastUsedAt() {
      return Optional.ofNullable(this.usage.getMostRecentUsageDate()).map(Date::toInstant);
   }

   public int totalUsages() {
      return this.usage.getTotalUsages();
   }

   @SuppressFBWarnings(justification = "generated code")
   FidoRegistrationCredential(
      @NonNull FidoCredentialId id,
      @NonNull FidoUserHandle userHandle,
      @NonNull FidoCredentialName name,
      @NonNull FidoCredentialDisplayName displayName,
      @NonNull FidoPublicKeyCredentialId publicKeyCredentialId,
      @NonNull FidoPublicKey publicKey,
      @NonNull FidoSignatureCounter signatureCounter,
      boolean residentRequested,
      @Nullable ResidentKeyRequirement residentKey,
      @NonNull FidoAttestationObject attestationObject,
      @NonNull Instant registeredAt,
      @NonNull TokenUsage usage,
      boolean locked,
      @NonNull Set<String> transports
   ) {
      if (id == null) {
         throw new NullPointerException("id is marked non-null but is null");
      }

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

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

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

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

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

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

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

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

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

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

      this.id = id;
      this.userHandle = userHandle;
      this.name = name;
      this.displayName = displayName;
      this.publicKeyCredentialId = publicKeyCredentialId;
      this.publicKey = publicKey;
      this.signatureCounter = signatureCounter;
      this.residentRequested = residentRequested;
      this.residentKey = residentKey;
      this.attestationObject = attestationObject;
      this.registeredAt = registeredAt;
      this.usage = usage;
      this.locked = locked;
      this.transports = transports;
   }

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

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

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

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public String toString() {
      return "FidoRegistrationCredential(id=" + this.getId() + ")";
   }

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

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoUserHandle getUserHandle() {
      return this.userHandle;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoCredentialName getName() {
      return this.name;
   }

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

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoPublicKeyCredentialId getPublicKeyCredentialId() {
      return this.publicKeyCredentialId;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoPublicKey getPublicKey() {
      return this.publicKey;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoSignatureCounter getSignatureCounter() {
      return this.signatureCounter;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isResidentRequested() {
      return this.residentRequested;
   }

   @Nullable
   @SuppressFBWarnings(justification = "generated code")
   public ResidentKeyRequirement getResidentKey() {
      return this.residentKey;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoAttestationObject getAttestationObject() {
      return this.attestationObject;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public boolean isLocked() {
      return this.locked;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public Set<String> getTransports() {
      return this.transports;
   }

   public static class ValidatingFidoRegistrationCredentialBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialId id;
      @SuppressFBWarnings(justification = "generated code")
      private FidoUserHandle userHandle;
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialName name;
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialDisplayName displayName;
      @SuppressFBWarnings(justification = "generated code")
      private FidoPublicKeyCredentialId publicKeyCredentialId;
      @SuppressFBWarnings(justification = "generated code")
      private FidoPublicKey publicKey;
      @SuppressFBWarnings(justification = "generated code")
      private FidoSignatureCounter signatureCounter;
      @SuppressFBWarnings(justification = "generated code")
      private boolean residentRequested;
      @SuppressFBWarnings(justification = "generated code")
      private ResidentKeyRequirement residentKey;
      @SuppressFBWarnings(justification = "generated code")
      private FidoAttestationObject attestationObject;
      @SuppressFBWarnings(justification = "generated code")
      private Instant registeredAt;
      @SuppressFBWarnings(justification = "generated code")
      private TokenUsage usage;
      @SuppressFBWarnings(justification = "generated code")
      private boolean locked;
      @SuppressFBWarnings(justification = "generated code")
      private Set<String> transports;

      public FidoRegistrationCredential build() {
         this.validateTransports();
         return new FidoRegistrationCredential(
            this.id,
            this.userHandle,
            this.name,
            this.displayName,
            this.publicKeyCredentialId,
            this.publicKey,
            this.signatureCounter,
            this.residentRequested,
            this.residentKey,
            this.attestationObject,
            this.registeredAt,
            this.usage,
            this.locked,
            this.transports
         );
      }

      private void validateTransports() {
         this.transports.forEach(FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder::validateTransport);
      }

      private static void validateTransport(String transport) {
         if (transport.length() > 50 || !FidoRegistrationCredential.VALID_TRANSPORT_PATTERN.matcher(transport).matches()) {
            throw new IllegalArgumentException("Invalid FIDO transport detected.");
         }
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder id(@NonNull FidoCredentialId 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 FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder userHandle(@NonNull FidoUserHandle userHandle) {
         if (userHandle == null) {
            throw new NullPointerException("userHandle is marked non-null but is null");
         }

         this.userHandle = userHandle;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder name(@NonNull FidoCredentialName name) {
         if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
         }

         this.name = name;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder displayName(@NonNull FidoCredentialDisplayName 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 FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder publicKeyCredentialId(
         @NonNull FidoPublicKeyCredentialId publicKeyCredentialId
      ) {
         if (publicKeyCredentialId == null) {
            throw new NullPointerException("publicKeyCredentialId is marked non-null but is null");
         }

         this.publicKeyCredentialId = publicKeyCredentialId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder publicKey(@NonNull FidoPublicKey publicKey) {
         if (publicKey == null) {
            throw new NullPointerException("publicKey is marked non-null but is null");
         }

         this.publicKey = publicKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder signatureCounter(@NonNull FidoSignatureCounter signatureCounter) {
         if (signatureCounter == null) {
            throw new NullPointerException("signatureCounter is marked non-null but is null");
         }

         this.signatureCounter = signatureCounter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder residentRequested(boolean residentRequested) {
         this.residentRequested = residentRequested;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder residentKey(@Nullable ResidentKeyRequirement residentKey) {
         this.residentKey = residentKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder attestationObject(@NonNull FidoAttestationObject attestationObject) {
         if (attestationObject == null) {
            throw new NullPointerException("attestationObject is marked non-null but is null");
         }

         this.attestationObject = attestationObject;
         return this;
      }

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

         this.registeredAt = registeredAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder usage(@NonNull TokenUsage usage) {
         if (usage == null) {
            throw new NullPointerException("usage is marked non-null but is null");
         }

         this.usage = usage;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder locked(boolean locked) {
         this.locked = locked;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder transports(@NonNull Set<String> transports) {
         if (transports == null) {
            throw new NullPointerException("transports is marked non-null but is null");
         }

         this.transports = transports;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FidoRegistrationCredential.ValidatingFidoRegistrationCredentialBuilder(id="
            + this.id
            + ", userHandle="
            + this.userHandle
            + ", name="
            + this.name
            + ", displayName="
            + this.displayName
            + ", publicKeyCredentialId="
            + this.publicKeyCredentialId
            + ", publicKey="
            + this.publicKey
            + ", signatureCounter="
            + this.signatureCounter
            + ", residentRequested="
            + this.residentRequested
            + ", residentKey="
            + this.residentKey
            + ", attestationObject="
            + this.attestationObject
            + ", registeredAt="
            + this.registeredAt
            + ", usage="
            + this.usage
            + ", locked="
            + this.locked
            + ", transports="
            + this.transports
            + ")";
      }
   }
}
