package com.airlock.iam.flow.shared.domain.model.session;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.domain.model.session.IamSession;
import com.airlock.iam.common.domain.model.session.SessionId;
import com.airlock.iam.common.domain.model.session.SessionNonces;
import com.airlock.iam.common.domain.model.state.SessionStateId;
import com.airlock.iam.flow.domain.model.session.StateMachineHolder;
import com.airlock.iam.flow.domain.model.statemachine.FlowStateMachine;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.function.Supplier;

public final class FlowBasedIamSession implements IamSession, StateMachineHolder {
   private final Supplier<SessionId> sessionId;
   private final SessionStateId sessionStateId;
   private Optional<SessionNonces> nonces;
   private Optional<ConfigurationContext> configurationContext;
   private final LazyUserSession lazyUserSession;
   private Optional<Supplier<FlowStateMachine>> stateMachine;
   private final boolean unpersisted;
   private final boolean lockAcquired;

   private FlowBasedIamSession(
      Supplier<SessionId> sessionId,
      SessionStateId sessionStateId,
      Optional<SessionNonces> nonces,
      Optional<ConfigurationContext> initialContext,
      LazyUserSession lazyUserSession,
      boolean unpersisted,
      boolean lockAcquired
   ) {
      this.sessionId = sessionId;
      this.sessionStateId = sessionStateId;
      this.nonces = nonces;
      this.configurationContext = initialContext;
      this.lazyUserSession = lazyUserSession;
      this.unpersisted = unpersisted;
      this.lockAcquired = lockAcquired;
      this.stateMachine = Optional.empty();
   }

   public static FlowBasedIamSession createFreshIamSession(Supplier<SessionId> sessionId) {
      return new FlowBasedIamSession(
         sessionId,
         SessionStateId.generateStateRepositoryKey(),
         Optional.of(SessionNonces.newSessionNonces()),
         Optional.empty(),
         LazyUserSession.alreadySupplied(new UserSessionImpl()),
         true,
         true
      );
   }

   public static FlowBasedIamSession createRestoredIamSession(
      SessionId sessionId, SessionStateId sessionStateId, SessionNonces nonces, ConfigurationContext context, LazyUserSession userSession
   ) {
      return new FlowBasedIamSession(() -> sessionId, sessionStateId, Optional.of(nonces), Optional.of(context), userSession, false, true);
   }

   public static FlowBasedIamSession createRestoredFromTicket(Supplier<SessionId> sessionId, ConfigurationContext context, LazyUserSession userSession) {
      return new FlowBasedIamSession(
         sessionId, SessionStateId.generateStateRepositoryKey(), Optional.of(SessionNonces.newSessionNonces()), Optional.of(context), userSession, true, true
      );
   }

   public static FlowBasedIamSession createIamSessionForLockNotAcquired(Supplier<SessionId> sessionId, SessionStateId sessionStateId) {
      return new FlowBasedIamSession(sessionId, sessionStateId, Optional.empty(), Optional.empty(), LazyUserSession.createUnsuppliable(), false, false);
   }

   public UserSessionImpl getUserSession() {
      return this.lazyUserSession.getUserSession();
   }

   public FlowBasedIamSession withLockNotAcquired() {
      return new FlowBasedIamSession(
         this.sessionId, this.sessionStateId, this.nonces, this.configurationContext, LazyUserSession.createUnsuppliable(), this.unpersisted, false
      );
   }

   @Override
   public Optional<UserId> userId() {
      return this.lazyUserSession.getUserId();
   }

   @Override
   public SessionStateId sessionStateId() {
      return this.sessionStateId;
   }

   @Override
   public SessionId sessionId() {
      return this.sessionId.get();
   }

   @Override
   public Optional<SessionNonces> sessionNonces() {
      return this.nonces;
   }

   @Override
   public void setSessionNonces(SessionNonces newNonces) {
      this.nonces = Optional.of(newNonces);
   }

   @Override
   public void setStateMachine(Supplier<FlowStateMachine> machine) {
      if (!this.lockAcquired) {
         throw new FailureException("Cannot add state machine if lock is not acquired.");
      }

      this.stateMachine = Optional.of(machine);
   }

   public void setConfigurationContext(ConfigurationContext context) {
      this.configurationContext = Optional.of(context);
   }

   public Supplier<SessionId> getSessionIdSupplier() {
      return this.sessionId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Supplier<SessionId> getSessionId() {
      return this.sessionId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public SessionStateId getSessionStateId() {
      return this.sessionStateId;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<SessionNonces> getNonces() {
      return this.nonces;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<ConfigurationContext> getConfigurationContext() {
      return this.configurationContext;
   }

   @SuppressFBWarnings(justification = "generated code")
   public LazyUserSession getLazyUserSession() {
      return this.lazyUserSession;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public Optional<Supplier<FlowStateMachine>> getStateMachine() {
      return this.stateMachine;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isUnpersisted() {
      return this.unpersisted;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean isLockAcquired() {
      return this.lockAcquired;
   }
}
