package com.airlock.iam.flow.shared.infrastructure.session;

import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.domain.model.mapification.MapificationUtils;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.service.mapification.Mapifier;
import com.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.domain.model.session.FlowSessionStatus;
import com.airlock.iam.flow.domain.model.session.InternalFlowSession;
import com.airlock.iam.flow.domain.model.session.MultiFlowSession;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSessionStatus;
import com.airlock.iam.flow.shared.domain.model.session.FlowSessionMapifier;
import com.airlock.iam.flow.shared.domain.model.session.MultiFlowSessionMapifier;
import com.airlock.iam.flow.shared.domain.model.session.UserSessionImpl;
import com.airlock.iam.flow.shared.domain.service.tag.TagMapifier;
import com.airlock.iam.flow.shared.infrastructure.service.UserSessionMapifier;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public final class UserSessionMapifierImpl implements UserSessionMapifier {
   private static final String USER_IDENTITY_KEY = "userId";
   private static final String ATTRIBUTES_KEY = "attrs";
   private static final String TAGS_KEY = "tags";
   private static final String STATUS_KEY = "status";
   private static final String FLOW_SESSION_KEY = "flowSession";
   private static final String MULTI_FLOW_SESSION_KEY = "multiFlowSession";
   private final Provider<Mapifier> mapifier;
   private final Provider<TagMapifier> tagMapifier;
   private final Provider<FlowSessionMapifier> flowSessionMapifier;
   private final Provider<MultiFlowSessionMapifier> multiFlowSessionMapifier;

   @Inject
   public UserSessionMapifierImpl(
      Provider<Mapifier> mapifier,
      Provider<TagMapifier> tagMapifier,
      Provider<FlowSessionMapifier> flowSessionMapifier,
      Provider<MultiFlowSessionMapifier> multiFlowSessionMapifier
   ) {
      this.mapifier = mapifier;
      this.tagMapifier = tagMapifier;
      this.flowSessionMapifier = flowSessionMapifier;
      this.multiFlowSessionMapifier = multiFlowSessionMapifier;
   }

   @Override
   public MapifiedValues mapifyUserSession(UserSession userSession) {
      UserSessionImpl session = (UserSessionImpl)userSession;
      MapifiedValuesBuilder mapified = MapifiedValuesBuilder.mapifiedValues()
         .putIfPresent("userId", session.userId().map(UserIdentityMapifier::mapifyUserIdentity))
         .put("status", session.getStatus().name())
         .putIfNotEmpty("attrs", MapificationUtils.mapifyCollection(session.userSessionAttributes(), ((Mapifier)this.mapifier.get())::mapify))
         .putIfNotEmpty("tags", this.mapifyTags(session.getTags()));
      if (session.getCurrentFlowSession().filter(fs -> fs.getStatus().isBefore(FlowSessionStatus.TERMINATED)).isPresent()) {
         mapified.putIfPresent(
               "multiFlowSession", session.getMultiFlowSession().map(((MultiFlowSessionMapifier)this.multiFlowSessionMapifier.get())::mapifyMultiFlowSession)
            )
            .putIfPresent("flowSession", session.getCurrentFlowSession().map(((FlowSessionMapifier)this.flowSessionMapifier.get())::mapifyFlowSession));
      }

      return mapified.build();
   }

   @Override
   public UserSessionImpl demapifyUserSession(MapifiedValues values) {
      return this.demapifyUserSession(values, false);
   }

   @Override
   public UserSessionImpl demapifyUserSessionIgnoringFlowSession(MapifiedValues values) {
      return this.demapifyUserSession(values, true);
   }

   @Override
   public boolean containsFlowSession(MapifiedValues userSessionValues) {
      return userSessionValues.containsKey("flowSession");
   }

   public static MapifiedValues withoutFlowSession(MapifiedValues userSessionValues) {
      if (!userSessionValues.containsKey("flowSession") && !userSessionValues.containsKey("multiFlowSession")) {
         return userSessionValues;
      }

      Map<String, Object> map = new HashMap<>(userSessionValues.asMap());
      map.remove("flowSession");
      map.remove("multiFlowSession");
      return new MapifiedValues(map);
   }

   @Override
   public boolean containsActiveFlowSession(MapifiedValues userSessionValues) {
      return this.containsFlowSession(userSessionValues)
         ? ((FlowSessionMapifier)this.flowSessionMapifier.get()).containsActiveFlowSession(userSessionValues.getValues("flowSession"))
         : false;
   }

   private UserSessionImpl demapifyUserSession(MapifiedValues values, boolean ignoreFlowSession) {
      return UserSessionImpl.restoreSession(
         values.demapifyOptional("userId", UserIdentityMapifier::demapifyUserIdentity),
         values.demapifySet("tags", map -> ((TagMapifier)this.tagMapifier.get()).demapifyTag(map)),
         values.demapifyList("attrs", v -> ((Mapifier)this.mapifier.get()).demapify(v)),
         ignoreFlowSession ? Optional.empty() : this.demapifyFlowSession(values),
         ignoreFlowSession ? Optional.empty() : values.demapifyOptional("multiFlowSession", this::demapifyMultiFlowSession),
         values.simpleValue("status", v -> UserSessionStatus.valueOf((String)v))
      );
   }

   public static Optional<UserId> extractUserId(MapifiedValues userSessionValues) {
      return userSessionValues.demapifyOptional("userId", UserIdentityMapifier::demapifyUserIdentity)
         .map(UserIdentity::id)
         .or(() -> userSessionValues.getOptionalValues("flowSession").flatMap(FlowSessionMapifierImpl::extractUserId));
   }

   private Optional<InternalFlowSession> demapifyFlowSession(MapifiedValues values) {
      return Optional.of(values)
         .flatMap(v -> v.getOptionalValues("flowSession"))
         .filter(v -> v.simpleValue("status", status -> FlowSessionStatus.valueOf((String)status)) != FlowSessionStatus.TERMINATED)
         .map(flowSession -> ((FlowSessionMapifier)this.flowSessionMapifier.get()).demapifyFlowSession(flowSession));
   }

   private List<MapifiedValues> mapifyTags(Set<Tag> tags) {
      TagMapifier serializer = (TagMapifier)this.tagMapifier.get();
      return tags.stream().map(serializer::mapifyTag).toList();
   }

   private MultiFlowSession demapifyMultiFlowSession(MapifiedValues values) {
      return ((MultiFlowSessionMapifier)this.multiFlowSessionMapifier.get()).demapifyMultiFlowSession(values);
   }
}
