package com.airlock.iam.flow.application.service.session;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.application.configuration.FlowConfigRepository;
import com.airlock.iam.flow.application.configuration.flow.FlowConfigHash;
import com.airlock.iam.flow.domain.model.flow.NoFlowStartedException;
import com.airlock.iam.flow.domain.model.session.FlowSessionRepository;
import com.airlock.iam.flow.domain.model.session.GenericMultiFlowSession;
import com.airlock.iam.flow.domain.model.session.InternalFlowSession;
import com.airlock.iam.flow.domain.model.session.MultiFlowSession;
import com.airlock.iam.flow.domain.service.MultiFlowManager;
import com.airlock.iam.flow.types.api.domain.model.session.MultiFlowType;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;

public final class GenericFlowSessionRepository implements FlowSessionRepository {
   private final MultiFlowManager multiFlowManager;
   private final Optional<FlowConfigHash> defaultFlowConfigHash;
   private final MultiFlowType multiFlowType;
   private final FlowConfigRepository flowConfigRepository;
   private final FlowContinuationChecker flowContinuationChecker;

   @Inject
   public GenericFlowSessionRepository(
      MultiFlowManager multiFlowManager,
      Optional<FlowConfigHash> defaultFlowConfigHash,
      MultiFlowType multiFlowType,
      FlowConfigRepository flowConfigRepository,
      FlowContinuationChecker flowContinuationChecker
   ) {
      this.multiFlowManager = multiFlowManager;
      this.defaultFlowConfigHash = defaultFlowConfigHash;
      this.multiFlowType = multiFlowType;
      this.flowConfigRepository = flowConfigRepository;
      this.flowContinuationChecker = flowContinuationChecker;
   }

   @Override
   public synchronized FlowSession getCurrentOrPrepareFirstFlowSession() {
      this.multiFlowManager.expectMultiFlowType(this.multiFlowType);
      return this.multiFlowManager.getCurrentFlowSession().isPresent()
         ? this.multiFlowManager.getCurrentFlowSession().get()
         : this.prepareNewDefaultFlowSession();
   }

   private InternalFlowSession prepareNewDefaultFlowSession() {
      this.checkNewFlowPossible();
      FlowConfigHash flowConfigHash = this.defaultFlowConfigHash.orElseThrow(NoFlowStartedException::new);
      return this.prepareFirstFlowSession(flowConfigHash);
   }

   public synchronized InternalFlowSession prepareNewFlowSession(FlowConfigHash flowConfigHash) {
      this.checkNewFlowPossible();
      return this.prepareFirstFlowSession(flowConfigHash);
   }

   private void checkNewFlowPossible() {
      this.flowContinuationChecker.ensureFlowStartAllowed();
      if (this.multiFlowManager.getCurrentFlowSession().isPresent()) {
         throw new FailureException("Cannot prepare a new flow session when there is already one.");
      }
   }

   private InternalFlowSession prepareFirstFlowSession(FlowConfigHash flowConfigHash) {
      MultiFlowSession session = GenericMultiFlowSession.createNewMultiFlowSession(
         this.flowConfigRepository.getFlowConfigByConfigHash(flowConfigHash).orElseThrow().getFlowId(), this.multiFlowType, List.of(flowConfigHash)
      );
      return this.multiFlowManager.prepareFirstFlowSession(session);
   }
}
