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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.NonNull;

public record LoginFromNewDeviceKnownUsers(@NonNull Map<UserId, LoginFromNewDeviceUserInfo> users) {
   public static final int MAX_USERS = 10;

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

      if (users.size() > 10) {
         throw new FailureException(String.format("The number of users is limited to %s.", 10));
      }
   }

   public static LoginFromNewDeviceKnownUsers empty() {
      return new LoginFromNewDeviceKnownUsers(Map.of());
   }

   public static LoginFromNewDeviceKnownUsers ofListOnOverflowDropOldest(@NonNull List<LoginFromNewDeviceUser> users) {
      if (users == null) {
         throw new NullPointerException("users is marked non-null but is null");
      } else {
         return new LoginFromNewDeviceKnownUsers(
            users.stream()
               .sorted(LoginFromNewDeviceUser.latestRenewedFirst())
               .limit(10L)
               .collect(Collectors.toMap(LoginFromNewDeviceUser::userId, LoginFromNewDeviceUser::userInfo))
         );
      }
   }

   public LoginFromNewDeviceKnownUsers withoutExpiredUsers(LoginFromNewDeviceValidity idleTimeout, Instant time) {
      return new LoginFromNewDeviceKnownUsers(
         this.users
            .entrySet()
            .stream()
            .filter(userInfo -> userInfo.getValue().isValid(idleTimeout, time))
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue))
      );
   }

   public LoginFromNewDeviceKnownUsers forUserSeenAt(UserId userId, Instant seenAt) {
      LoginFromNewDeviceUser currentUser = this.renewOrCreateUser(userId, seenAt);
      Stream<LoginFromNewDeviceUser> otherUsers = this.usersList().stream().filter(u -> !u.userId().equals(userId));
      return ofListOnOverflowDropOldest(Stream.concat(otherUsers, Stream.of(currentUser)).toList());
   }

   public Optional<LoginFromNewDeviceUserInfo> findUser(UserId userId) {
      return Optional.ofNullable(this.users.get(userId));
   }

   private List<LoginFromNewDeviceUser> usersList() {
      return this.users.entrySet().stream().map(entry -> new LoginFromNewDeviceUser(entry.getKey(), entry.getValue())).toList();
   }

   private LoginFromNewDeviceUser renewOrCreateUser(UserId userId, Instant time) {
      return this.findUser(userId)
         .map(user -> LoginFromNewDeviceUser.of(userId, user.createdAt(), LoginFromNewDeviceRenewedAt.of(time)))
         .orElse(LoginFromNewDeviceUser.of(userId, LoginFromNewDeviceCreatedAt.of(time), LoginFromNewDeviceRenewedAt.of(time)));
   }
}
