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

import com.airlock.iam.base.api.domain.model.mapification.Mapifiable;
import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.domain.model.mapification.MapificationUtils;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.factor.domain.model.fido.user.FidoUserPrincipal;
import com.airlock.iam.factor.domain.model.fido.user.FidoUserPrincipalMapifier;
import com.airlock.iam.fido.api.domain.model.FidoPublicKeyCredentialId;
import com.airlock.iam.fido.api.domain.model.FidoRelyingPartyId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.NonNull;

public final class FidoPublicKeyCredentialCreationOptions implements Mapifiable {
   private static final String CHALLENGE_KEY = "challenge";
   private static final String CREATED_AT_KEY = "createdAt";
   private static final String REGISTRATION_SETTINGS_KEY = "registrationSettings";
   private static final String FIDO_USER_KEY = "fidoUser";
   private static final String CREDENTIAL_DISPLAY_NAME_KEY = "credentialDisplayName";
   private static final String CREDENTIAL_IDS_KEY = "excludedCredentialIds";
   @NonNull
   private final FidoCredentialRegistrationSettings registrationSettings;
   @NonNull
   private final FidoUserPrincipal fidoUser;
   @NonNull
   private final FidoCredentialDisplayName credentialDisplayName;
   @NonNull
   private final FidoChallenge challenge;
   @NonNull
   private final List<FidoPublicKeyCredentialId> excludedCredentialIds;
   @NonNull
   private final Instant createdAt;
   @NonNull
   private final Instant validUntil;

   public FidoPublicKeyCredentialCreationOptions(
      @NonNull FidoCredentialRegistrationSettings registrationSettings,
      @NonNull FidoUserPrincipal fidoUser,
      @NonNull FidoCredentialDisplayName credentialDisplayName,
      @NonNull FidoChallenge challenge,
      @NonNull List<FidoPublicKeyCredentialId> excludedCredentialIds,
      @NonNull Instant createdAt
   ) {
      if (registrationSettings == null) {
         throw new NullPointerException("registrationSettings is marked non-null but is null");
      }

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

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

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

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

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

      this.registrationSettings = registrationSettings;
      this.fidoUser = fidoUser;
      this.credentialDisplayName = credentialDisplayName;
      this.challenge = challenge;
      this.excludedCredentialIds = excludedCredentialIds;
      this.createdAt = createdAt;
      this.validUntil = createdAt.plusMillis(registrationSettings.getRegistrationTimeout().toMillis());
   }

   public boolean isValidAt(Instant now) {
      return !now.isBefore(this.createdAt) && !now.isAfter(this.validUntil);
   }

   public FidoRelyingPartyId getRelyingPartyId() {
      return this.fidoUser.getRelyingPartyId();
   }

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("registrationSettings", FidoCredentialRegistrationSettingsMapifier.mapifyFidoCredentialRegistrationSettings(this.registrationSettings))
         .put("fidoUser", FidoUserPrincipalMapifier.mapifyFidoUserPrincipal(this.fidoUser))
         .put("credentialDisplayName", this.credentialDisplayName.getName())
         .put("challenge", this.challenge.mapify())
         .putIfNotEmpty("excludedCredentialIds", MapificationUtils.mapifyCollection(this.excludedCredentialIds, FidoPublicKeyCredentialId::base64Url))
         .put("createdAt", MapificationUtils.mapifyInstant(this.createdAt))
         .build();
   }

   public static FidoPublicKeyCredentialCreationOptions demapify(MapifiedValues values) {
      return new FidoPublicKeyCredentialCreationOptions(
         values.demapifyValue("registrationSettings", FidoCredentialRegistrationSettingsMapifier::demapifyFidoCredentialRegistrationSettings),
         values.demapifyValue("fidoUser", FidoUserPrincipalMapifier::demapifyFidoUserPrincipal),
         FidoCredentialDisplayName.credentialDisplayName(values.get("credentialDisplayName")),
         values.demapifyValue("challenge", FidoChallenge::demapify),
         values.<String>getList("excludedCredentialIds").stream().map(FidoPublicKeyCredentialId::publicKeyCredentialId).toList(),
         values.demapifyValue("createdAt", MapificationUtils::demapifyInstant)
      );
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder toBuilder() {
      FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder builder = new FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder()
         .registrationSettings(this.registrationSettings)
         .fidoUser(this.fidoUser)
         .credentialDisplayName(this.credentialDisplayName)
         .challenge(this.challenge)
         .createdAt(this.createdAt);
      if (this.excludedCredentialIds != null) {
         builder.excludedCredentialIds(this.excludedCredentialIds);
      }

      return builder;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof FidoPublicKeyCredentialCreationOptions other)) {
         return false;
      } else {
         Object this$registrationSettings = this.registrationSettings;
         Object other$registrationSettings = other.registrationSettings;
         if (this$registrationSettings == null ? other$registrationSettings == null : this$registrationSettings.equals(other$registrationSettings)) {
            Object this$fidoUser = this.fidoUser;
            Object other$fidoUser = other.fidoUser;
            if (this$fidoUser == null ? other$fidoUser == null : this$fidoUser.equals(other$fidoUser)) {
               Object this$credentialDisplayName = this.credentialDisplayName;
               Object other$credentialDisplayName = other.credentialDisplayName;
               if (this$credentialDisplayName == null ? other$credentialDisplayName == null : this$credentialDisplayName.equals(other$credentialDisplayName)) {
                  Object this$challenge = this.challenge;
                  Object other$challenge = other.challenge;
                  if (this$challenge == null ? other$challenge == null : this$challenge.equals(other$challenge)) {
                     Object this$excludedCredentialIds = this.excludedCredentialIds;
                     Object other$excludedCredentialIds = other.excludedCredentialIds;
                     if (this$excludedCredentialIds == null
                        ? other$excludedCredentialIds == null
                        : this$excludedCredentialIds.equals(other$excludedCredentialIds)) {
                        Object this$createdAt = this.createdAt;
                        Object other$createdAt = other.createdAt;
                        if (this$createdAt == null ? other$createdAt == null : this$createdAt.equals(other$createdAt)) {
                           Object this$validUntil = this.validUntil;
                           Object other$validUntil = other.validUntil;
                           return this$validUntil == null ? other$validUntil == null : this$validUntil.equals(other$validUntil);
                        } else {
                           return false;
                        }
                     } 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 $registrationSettings = this.registrationSettings;
      result = result * 59 + ($registrationSettings == null ? 43 : $registrationSettings.hashCode());
      Object $fidoUser = this.fidoUser;
      result = result * 59 + ($fidoUser == null ? 43 : $fidoUser.hashCode());
      Object $credentialDisplayName = this.credentialDisplayName;
      result = result * 59 + ($credentialDisplayName == null ? 43 : $credentialDisplayName.hashCode());
      Object $challenge = this.challenge;
      result = result * 59 + ($challenge == null ? 43 : $challenge.hashCode());
      Object $excludedCredentialIds = this.excludedCredentialIds;
      result = result * 59 + ($excludedCredentialIds == null ? 43 : $excludedCredentialIds.hashCode());
      Object $createdAt = this.createdAt;
      result = result * 59 + ($createdAt == null ? 43 : $createdAt.hashCode());
      Object $validUntil = this.validUntil;
      return result * 59 + ($validUntil == null ? 43 : $validUntil.hashCode());
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public String toString() {
      return "FidoPublicKeyCredentialCreationOptions(registrationSettings="
         + this.getRegistrationSettings()
         + ", fidoUser="
         + this.getFidoUser()
         + ", credentialDisplayName="
         + this.getCredentialDisplayName()
         + ", challenge="
         + this.getChallenge()
         + ", excludedCredentialIds="
         + this.getExcludedCredentialIds()
         + ", createdAt="
         + this.createdAt
         + ", validUntil="
         + this.validUntil
         + ")";
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoCredentialRegistrationSettings getRegistrationSettings() {
      return this.registrationSettings;
   }

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

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

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public FidoChallenge getChallenge() {
      return this.challenge;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public List<FidoPublicKeyCredentialId> getExcludedCredentialIds() {
      return this.excludedCredentialIds;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class FidoPublicKeyCredentialCreationOptionsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialRegistrationSettings registrationSettings;
      @SuppressFBWarnings(justification = "generated code")
      private FidoUserPrincipal fidoUser;
      @SuppressFBWarnings(justification = "generated code")
      private FidoCredentialDisplayName credentialDisplayName;
      @SuppressFBWarnings(justification = "generated code")
      private FidoChallenge challenge;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<FidoPublicKeyCredentialId> excludedCredentialIds;
      @SuppressFBWarnings(justification = "generated code")
      private Instant createdAt;

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

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder registrationSettings(
         @NonNull FidoCredentialRegistrationSettings registrationSettings
      ) {
         if (registrationSettings == null) {
            throw new NullPointerException("registrationSettings is marked non-null but is null");
         }

         this.registrationSettings = registrationSettings;
         return this;
      }

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

         this.credentialDisplayName = credentialDisplayName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder challenge(@NonNull FidoChallenge challenge) {
         if (challenge == null) {
            throw new NullPointerException("challenge is marked non-null but is null");
         }

         this.challenge = challenge;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder excludedCredentialId(
         FidoPublicKeyCredentialId excludedCredentialId
      ) {
         if (this.excludedCredentialIds == null) {
            this.excludedCredentialIds = new ArrayList<>();
         }

         this.excludedCredentialIds.add(excludedCredentialId);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder excludedCredentialIds(
         Collection<? extends FidoPublicKeyCredentialId> excludedCredentialIds
      ) {
         if (excludedCredentialIds == null) {
            throw new NullPointerException("excludedCredentialIds cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder clearExcludedCredentialIds() {
         if (this.excludedCredentialIds != null) {
            this.excludedCredentialIds.clear();
         }

         return this;
      }

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

         this.createdAt = createdAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public FidoPublicKeyCredentialCreationOptions build() {
         return new FidoPublicKeyCredentialCreationOptions(
            this.registrationSettings,
            this.fidoUser,
            this.credentialDisplayName,
            this.challenge,
            switch (this.excludedCredentialIds == null ? 0 : this.excludedCredentialIds.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.excludedCredentialIds.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.excludedCredentialIds));
            },
            this.createdAt
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FidoPublicKeyCredentialCreationOptions.FidoPublicKeyCredentialCreationOptionsBuilder(registrationSettings="
            + this.registrationSettings
            + ", fidoUser="
            + this.fidoUser
            + ", credentialDisplayName="
            + this.credentialDisplayName
            + ", challenge="
            + this.challenge
            + ", excludedCredentialIds="
            + this.excludedCredentialIds
            + ", createdAt="
            + this.createdAt
            + ")";
      }
   }
}
