package com.airlock.iam.authentication.domain.service.flow;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.service.SingleRequestPerUserService;
import com.airlock.iam.common.domain.service.exception.ExclusiveOperationDeniedException;
import com.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.application.configuration.flow.FlowConfigHash;
import com.airlock.iam.flow.domain.model.flow.ExclusiveRightToOperateOnUserException;
import com.airlock.iam.flow.domain.model.session.InternalFlowSession;
import com.airlock.iam.flow.domain.service.session.UserIdentifiedUponFlowStart;
import com.airlock.iam.flow.shared.api.domain.model.session.UserDependentFlowSession;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.flow.shared.domain.model.session.FlowSessionImpl;
import com.airlock.iam.flow.shared.domain.model.session.MultiFlowCapableUserSession;
import com.airlock.iam.flow.shared.domain.model.session.PublicFlowsCompletedInformation;
import com.airlock.iam.flow.shared.domain.service.FlowSessionUserSessionConnector;
import com.airlock.iam.flow.shared.domain.service.SessionAttributesHelper;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public final class AuthenticationFlowSessionUserSessionConnector implements FlowSessionUserSessionConnector {
   private final Provider<SigningLogger> signingLogger;
   private final Provider<SingleRequestPerUserService> singleRequestPerUserService;

   @Inject
   public AuthenticationFlowSessionUserSessionConnector(
      Provider<SigningLogger> signingLogger, Provider<SingleRequestPerUserService> singleRequestPerUserService
   ) {
      this.signingLogger = signingLogger;
      this.singleRequestPerUserService = singleRequestPerUserService;
   }

   @Override
   public InternalFlowSession createFlowSessionFromUserSession(FlowConfigHash flowConfigHash, MultiFlowCapableUserSession userSession) throws ExclusiveRightToOperateOnUserException {
      FlowSessionImpl flowSession = new FlowSessionImpl(flowConfigHash, userSession.getTags());
      userSession.userId().ifPresent(userId -> {
         flowSession.setUserIdentity(userId);
         this.startOperatingExclusively(userId);
         flowSession.flowAttributes().putOrMergeAttribute(new UserIdentifiedUponFlowStart());
         ((SigningLogger)this.signingLogger.get()).log("Initializing flow session with user Id \"" + userId + "\" from user session.");
      });
      this.initializeFromPublicFlowCompletion(userSession, flowSession);
      SessionAttributesHelper.takeOverAttributesFromUserSessionToFlowSession(userSession, flowSession);
      return flowSession;
   }

   private void startOperatingExclusively(UserIdentity userId) {
      try {
         ((SingleRequestPerUserService)this.singleRequestPerUserService.get()).startOperatingExclusively(userId.id());
      } catch (ExclusiveOperationDeniedException e) {
         throw new ExclusiveRightToOperateOnUserException(userId.id());
      }
   }

   private void initializeFromPublicFlowCompletion(UserSession userSession, FlowSessionImpl flowSession) {
      Optional<PublicFlowsCompletedInformation> completedFlowInformation = userSession.findUserSessionAttribute(PublicFlowsCompletedInformation.class);
      if (userSession.userId().isEmpty() && completedFlowInformation.isPresent()) {
         UserIdentity userIdentity = completedFlowInformation.get().getUserIdentity();
         Set<Tag> tags = completedFlowInformation.get().getTags();
         flowSession.setUserIdentity(userIdentity);
         this.startOperatingExclusively(userIdentity);
         flowSession.getFlowSessionTags().addTags(tags);
         ((SigningLogger)this.signingLogger.get())
            .log(
               "Initializing flow session with user Id "
                  + LogUtil.logSafeQuote(userIdentity.id())
                  + " and tags "
                  + tags.stream().map(Tag::name).collect(Collectors.toSet())
                  + " from previous completed flow."
            );
      }

      userSession.removeUserSessionAttribute(PublicFlowsCompletedInformation.class);
   }

   @Override
   public void finalizeFlowSessionToUserSession(InternalFlowSession flowSession, MultiFlowCapableUserSession userSession) {
      Set<Tag> tagsToAdd = flowSession.getFlowSessionTags().getTagsToAdd();
      tagsToAdd.forEach(Tag::startTagTimeout);
      userSession.addTags(tagsToAdd);
      flowSession.getFlowSessionTags().getTagsToRemove().forEach(userSession::removeTagByName);
      SessionAttributesHelper.takeOverAttributesFromFlowSessionToUserSession(flowSession, userSession);
      if (flowSession instanceof UserDependentFlowSession) {
         ((UserDependentFlowSession)flowSession).getUserIdentity().ifPresent(userSession::setUserId);
      }
   }
}
