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

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.domain.model.event.EventCreationData;
import com.airlock.iam.base.domain.model.event.EventPublisher;
import com.airlock.iam.common.api.domain.model.contextdata.ContextData;
import com.airlock.iam.common.api.domain.model.contextdata.ContextDataItem;
import com.airlock.iam.common.api.domain.model.event.ContextDataChanged;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ContextDataChangeEventFactoryImpl implements ContextDataChangeEventFactory {
   private final Provider<EventCreationData> creationData;
   private final Provider<EventPublisher> eventPublisher;

   @Inject
   public ContextDataChangeEventFactoryImpl(Provider<EventCreationData> creationData, Provider<EventPublisher> eventPublisher) {
      this.creationData = creationData;
      this.eventPublisher = eventPublisher;
   }

   @Override
   public void publishContextDataChanged(UserId userId, Set<ContextDataChanged.ContextDataChange> changes) {
      ((EventPublisher)this.eventPublisher.get()).publish(new ContextDataChangedImpl((EventCreationData)this.creationData.get(), userId, changes));
   }

   @Override
   public void publishContextDataChanged(UserId userId, ContextData before, ContextData after) {
      SetView<String> allKeys = Sets.union(before.asKeyValueMap().keySet(), after.asKeyValueMap().keySet());
      Set<ContextDataChanged.ContextDataChange> changes = allKeys.stream().flatMap(key -> collectChanges(key, before, after)).collect(Collectors.toSet());
      if (!changes.isEmpty()) {
         this.publishContextDataChanged(userId, changes);
      }
   }

   private static Stream<ContextDataChanged.ContextDataChange> collectChanges(String key, ContextData before, ContextData after) {
      Object valueBefore = valueOrNull(before.find(key));
      Object valueAfter = valueOrNull(after.find(key));
      return !Objects.equals(valueBefore, valueAfter) ? Stream.of(new ContextDataChanged.ContextDataChange(key, valueBefore, valueAfter)) : Stream.empty();
   }

   @Nullable
   private static Object valueOrNull(Optional<ContextDataItem<Object>> item) {
      return item.map(ContextDataItem::getValue).orElse(null);
   }
}
