package com.airlock.iam.common.domain.model.session;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import lombok.NonNull;

public record SessionNonces(@NonNull SessionNonce current, @NonNull Map<SessionNonce, Instant> previous) {
   private static final Duration GRACE_PERIOD = Duration.ofSeconds(2L);

   @SuppressFBWarnings(justification = "generated code")
   public SessionNonces {
      if (current == null) {
         throw new NullPointerException("current is marked non-null but is null");
      }

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

   public static SessionNonces newSessionNonces() {
      return new SessionNonces(SessionNonce.newSessionNonce(), Collections.emptyMap());
   }

   public SessionNonces withRenewedNonce() {
      Map<SessionNonce, Instant> newPrevious = new HashMap<>();
      this.previous.entrySet().stream().filter(e -> e.getValue().isAfter(Instant.now())).forEach(e -> newPrevious.put(e.getKey(), e.getValue()));
      newPrevious.put(this.current, Instant.now().plus(GRACE_PERIOD));
      return new SessionNonces(SessionNonce.newSessionNonce(), Map.copyOf(newPrevious));
   }

   public SessionNonces withoutExpiredNonces() {
      Map<SessionNonce, Instant> newPrevious = this.previous
         .entrySet()
         .stream()
         .filter(e -> e.getValue().isAfter(Instant.now()))
         .collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
      return new SessionNonces(this.current, newPrevious);
   }

   public boolean isValid(SessionNonce nonce) {
      return this.current.equals(nonce) || this.previous.containsKey(nonce) && this.previous.get(nonce).isAfter(Instant.now());
   }
}
