package com.airlock.iam.flow.shared.domain.model.rememberme;

import com.airlock.iam.base.api.domain.model.Entity;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.time.TimeProvider;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import lombok.NonNull;

public final class RememberMeToken implements Entity<RememberMeToken> {
   @NonNull
   private final RememberMeTokenId id;
   @NonNull
   private final UserId iamUserId;
   @NonNull
   private RememberMeSecretHash secretHash;
   @NonNull
   private final Instant firstUsedAt;
   private Optional<Instant> lastUsedAt;
   @NonNull
   private final RememberMeRequestMetadata firstRequest;
   @NonNull
   private Optional<RememberMeRequestMetadata> latestRequest;
   @NonNull
   private final TimeProvider time;

   private RememberMeToken(
      @NonNull RememberMeTokenId id,
      @NonNull UserId iamUserId,
      @NonNull RememberMeSecretHash secretHash,
      @NonNull Instant firstUsedAt,
      @Nullable Instant lastUsedAt,
      @NonNull RememberMeRequestMetadata firstRequest,
      @Nullable RememberMeRequestMetadata latestRequest,
      @NonNull TimeProvider time
   ) {
      if (id == null) {
         throw new NullPointerException("id is marked non-null but is null");
      }

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

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

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

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

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

      this.id = id;
      this.iamUserId = iamUserId;
      this.secretHash = secretHash;
      this.firstRequest = firstRequest;
      this.firstUsedAt = firstUsedAt;
      this.lastUsedAt = Optional.ofNullable(lastUsedAt);
      this.latestRequest = Optional.ofNullable(latestRequest);
      this.time = time;
   }

   public RememberMeSecret renewSecret(RememberMeRequestMetadata request) {
      RememberMeSecret newSecret = RememberMeSecret.randomRememberMeSecret();
      this.secretHash = newSecret.hash();
      this.lastUsedAt = Optional.of(this.time.getCurrentInstant());
      this.latestRequest = Optional.of(request);
      return newSecret;
   }

   public boolean isExpired(RememberMeTokenLifetime tokenLifetime) {
      return this.isLifetimeExceeded(tokenLifetime.lifetime()) || this.isIdleLifetimeExceeded(tokenLifetime.idleLifetime());
   }

   private boolean isLifetimeExceeded(Duration lifetime) {
      return this.time.getCurrentInstant().isAfter(this.firstUsedAt().plus(lifetime));
   }

   private boolean isIdleLifetimeExceeded(Optional<Duration> idleLifetime) {
      return idleLifetime.<Boolean>map(t -> this.time.getCurrentInstant().isAfter(this.lastUsedAt().orElse(this.firstUsedAt()).plus(t))).orElse(false);
   }

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

   @Override
   public String toString() {
      return String.format("RememberMeToken[user='%s', id='%s', hash='%s']", this.iamUserId, this.getId(), this.secretHash);
   }

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

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

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public RememberMeSecretHash secretHash() {
      return this.secretHash;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public Optional<Instant> lastUsedAt() {
      return this.lastUsedAt;
   }

   @NonNull
   @SuppressFBWarnings(justification = "generated code")
   public RememberMeRequestMetadata firstRequest() {
      return this.firstRequest;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class RememberMeTokenBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeTokenId id;
      @SuppressFBWarnings(justification = "generated code")
      private UserId iamUserId;
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeSecretHash secretHash;
      @SuppressFBWarnings(justification = "generated code")
      private Instant firstUsedAt;
      @SuppressFBWarnings(justification = "generated code")
      private Instant lastUsedAt;
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeRequestMetadata firstRequest;
      @SuppressFBWarnings(justification = "generated code")
      private RememberMeRequestMetadata latestRequest;
      @SuppressFBWarnings(justification = "generated code")
      private TimeProvider time;

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

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder id(@NonNull RememberMeTokenId 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 RememberMeToken.RememberMeTokenBuilder iamUserId(@NonNull UserId iamUserId) {
         if (iamUserId == null) {
            throw new NullPointerException("iamUserId is marked non-null but is null");
         }

         this.iamUserId = iamUserId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder secretHash(@NonNull RememberMeSecretHash secretHash) {
         if (secretHash == null) {
            throw new NullPointerException("secretHash is marked non-null but is null");
         }

         this.secretHash = secretHash;
         return this;
      }

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

         this.firstUsedAt = firstUsedAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder lastUsedAt(@Nullable Instant lastUsedAt) {
         this.lastUsedAt = lastUsedAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder firstRequest(@NonNull RememberMeRequestMetadata firstRequest) {
         if (firstRequest == null) {
            throw new NullPointerException("firstRequest is marked non-null but is null");
         }

         this.firstRequest = firstRequest;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder latestRequest(@Nullable RememberMeRequestMetadata latestRequest) {
         this.latestRequest = latestRequest;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken.RememberMeTokenBuilder time(@NonNull TimeProvider time) {
         if (time == null) {
            throw new NullPointerException("time is marked non-null but is null");
         }

         this.time = time;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RememberMeToken build() {
         return new RememberMeToken(
            this.id, this.iamUserId, this.secretHash, this.firstUsedAt, this.lastUsedAt, this.firstRequest, this.latestRequest, this.time
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "RememberMeToken.RememberMeTokenBuilder(id="
            + this.id
            + ", iamUserId="
            + this.iamUserId
            + ", secretHash="
            + this.secretHash
            + ", firstUsedAt="
            + this.firstUsedAt
            + ", lastUsedAt="
            + this.lastUsedAt
            + ", firstRequest="
            + this.firstRequest
            + ", latestRequest="
            + this.latestRequest
            + ", time="
            + this.time
            + ")";
      }
   }
}
