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

import com.airlock.iam.authentication.domain.model.event.AuthenticationFlowEventFactory;
import com.airlock.iam.authentication.domain.model.session.SuccessfulAuthenticationMethods;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.application.configuration.flow.FlowConfigHash;
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.FlowCompletionService;
import com.airlock.iam.flow.shared.domain.model.session.FlowBasedIamSession;
import com.airlock.iam.flow.shared.domain.service.session.SessionTicketPersist;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

public final class ApplicationAccessSessionService implements FlowCompletionService {
   private static final ELogger LOG = ELoggerFactory.getLogger(ApplicationAccessSessionService.class);
   private final Provider<PrincipalInfo> user;
   private final Provider<AuthenticationFlowEventFactory> eventFactory;
   private final Provider<Optional<MultiFlowSession>> multiFlowSession;
   private final Provider<Optional<SessionTicketPersist>> userSessionPersistence;
   private final Provider<InternalFlowSession> flowSession;
   private final Provider<FlowBasedIamSession> userSession;
   private final Provider<ApplicationAccessCompletedService> accessCompletedService;

   @Inject
   public ApplicationAccessSessionService(
      Provider<PrincipalInfo> user,
      Provider<AuthenticationFlowEventFactory> eventFactory,
      Provider<Optional<MultiFlowSession>> multiFlowSession,
      Provider<Optional<SessionTicketPersist>> userSessionPersistence,
      Provider<InternalFlowSession> flowSession,
      Provider<FlowBasedIamSession> userSession,
      Provider<ApplicationAccessCompletedService> accessCompletedService
   ) {
      this.user = user;
      this.eventFactory = eventFactory;
      this.multiFlowSession = multiFlowSession;
      this.userSessionPersistence = userSessionPersistence;
      this.flowSession = flowSession;
      this.userSession = userSession;
      this.accessCompletedService = accessCompletedService;
   }

   @Override
   public void handleSuccessfulFlowCompletion() {
      if (!((Optional)this.multiFlowSession.get()).isEmpty()) {
         MultiFlowSession session = (MultiFlowSession)((Optional)this.multiFlowSession.get()).get();
         LOG.debug("Handling authorization session for application " + LogUtil.quote(session.getCurrentFlowId()));
         FlowConfigHash completedFlowConfigHash = ((InternalFlowSession)this.flowSession.get()).getFlowConfigHash();
         if (!session.getAllConfigHashes().contains(completedFlowConfigHash)) {
            throw new ViolationException("Completed a flow that is not part of current application access");
         }

         if (session.getAllConfigHashes().getFirst().equals(completedFlowConfigHash)) {
            this.handleAuthenticationFlowCompleted(session);
         } else {
            ((ApplicationAccessCompletedService)this.accessCompletedService.get()).handleApplicationAccessCompleted();
         }

         ((Optional)this.userSessionPersistence.get()).ifPresent(persistence -> persistence.persist((FlowBasedIamSession)this.userSession.get()));
      }
   }

   private void handleAuthenticationFlowCompleted(MultiFlowSession session) {
      LOG.debug("Handling completed authentication flow for application " + LogUtil.quote(session.getCurrentFlowId()));
      this.publishAuthenticationFlowSuccessfullyCompleted(this.loadSuccessfulAuthenticationMethods());
      if (!session.hasNextFlow()) {
         ((ApplicationAccessCompletedService)this.accessCompletedService.get()).handleApplicationAccessCompleted();
      } else {
         LOG.debug("Continuing with authorization flow");
      }
   }

   private SuccessfulAuthenticationMethods loadSuccessfulAuthenticationMethods() {
      return ((InternalFlowSession)this.flowSession.get())
         .flowAttributes()
         .tryGetAttribute(SuccessfulAuthenticationMethods.class)
         .orElseThrow(() -> new FailureException("Expecting [" + SuccessfulAuthenticationMethods.class.getSimpleName() + "] to be a present flow attribute"));
   }

   private void publishAuthenticationFlowSuccessfullyCompleted(SuccessfulAuthenticationMethods authMethods) {
      ((AuthenticationFlowEventFactory)this.eventFactory.get())
         .publishAuthenticationFlowSuccessfullyCompletedEvent(
            ((PrincipalInfo)this.user.get()).getAuthenticatedIdentity().orElseThrow(), authMethods.getAuthenticationMethods()
         );
   }
}
