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.user.FidoUserPrincipal;
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 java.time.Instant;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
import lombok.NonNull;

public final class FidoRegisteredCredential implements FidoCredential {
   @NonNull
   private final FidoCredentialId id;
   @NonNull
   private final FidoPublicKeyCredentialId publicKeyCredentialId;
   @NonNull
   private final FidoUserPrincipal fidoUser;
   @NonNull
   private final FidoPublicKey publicKey;
   @NonNull
   private FidoSignatureCounter signatureCounter;
   @NonNull
   private TokenUsage usage;
   private final boolean locked;
   @NonNull
   private final Set<String> transports;

   public FidoCredentialId getId() {
      return this.id;
   }

   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 Integer totalUsages() {
      return this.usage.getTotalUsages();
   }

   public void updateUsageStatistics(FidoSignatureCounter newSignatureCounter) {
      this.usage = TokenUsage.usedAt(this.usage, new Date());
      this.signatureCounter = newSignatureCounter;
   }

   @Override
   public String toString() {
      return String.format(
         "FIDO credential(ID='%s', public-key credential ID='%s', relying party ID='%s')",
         this.id,
         this.publicKeyCredentialId,
         this.fidoUser.getRelyingPartyId()
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   FidoRegisteredCredential(
      @NonNull FidoCredentialId id,
      @NonNull FidoPublicKeyCredentialId publicKeyCredentialId,
      @NonNull FidoUserPrincipal fidoUser,
      @NonNull FidoPublicKey publicKey,
      @NonNull FidoSignatureCounter signatureCounter,
      @NonNull TokenUsage usage,
      boolean locked,
      @NonNull Set<String> transports
   ) {
      if (id == null) {
         throw new NullPointerException("id is marked non-null but is null");
      }

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

      if (fidoUser == null) {
         throw new NullPointerException("fidoUser 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 (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.publicKeyCredentialId = publicKeyCredentialId;
      this.fidoUser = fidoUser;
      this.publicKey = publicKey;
      this.signatureCounter = signatureCounter;
      this.usage = usage;
      this.locked = locked;
      this.transports = transports;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof FidoRegisteredCredential 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());
   }

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

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoUserPrincipal getFidoUser() {
      return this.fidoUser;
   }

   @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 isLocked() {
      return this.locked;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class FidoRegisteredCredentialBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialId id;
      @SuppressFBWarnings(justification = "generated code")
      private FidoPublicKeyCredentialId publicKeyCredentialId;
      @SuppressFBWarnings(justification = "generated code")
      private FidoUserPrincipal fidoUser;
      @SuppressFBWarnings(justification = "generated code")
      private FidoPublicKey publicKey;
      @SuppressFBWarnings(justification = "generated code")
      private FidoSignatureCounter signatureCounter;
      @SuppressFBWarnings(justification = "generated code")
      private TokenUsage usage;
      @SuppressFBWarnings(justification = "generated code")
      private boolean locked;
      @SuppressFBWarnings(justification = "generated code")
      private Set<String> transports;

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

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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 FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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 FidoRegisteredCredential.FidoRegisteredCredentialBuilder fidoUser(@NonNull FidoUserPrincipal fidoUser) {
         if (fidoUser == null) {
            throw new NullPointerException("fidoUser is marked non-null but is null");
         }

         this.fidoUser = fidoUser;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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 FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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 FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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 FidoRegisteredCredential.FidoRegisteredCredentialBuilder locked(boolean locked) {
         this.locked = locked;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoRegisteredCredential.FidoRegisteredCredentialBuilder 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")
      public FidoRegisteredCredential build() {
         return new FidoRegisteredCredential(
            this.id, this.publicKeyCredentialId, this.fidoUser, this.publicKey, this.signatureCounter, this.usage, this.locked, this.transports
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FidoRegisteredCredential.FidoRegisteredCredentialBuilder(id="
            + this.id
            + ", publicKeyCredentialId="
            + this.publicKeyCredentialId
            + ", fidoUser="
            + this.fidoUser
            + ", publicKey="
            + this.publicKey
            + ", signatureCounter="
            + this.signatureCounter
            + ", usage="
            + this.usage
            + ", locked="
            + this.locked
            + ", transports="
            + this.transports
            + ")";
      }
   }
}
