package com.airlock.iam.admin.infrastructure.session;

import com.airlock.iam.admin.domain.model.session.AdminappSession;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.session.ReadOnlyIamSessionData;
import com.airlock.iam.common.domain.model.session.SessionNonces;
import com.airlock.iam.common.domain.model.session.SessionTimeoutSettings;
import com.airlock.iam.common.domain.model.state.FindResult;
import com.airlock.iam.common.domain.model.state.InsertResult;
import com.airlock.iam.common.domain.model.state.LockResult;
import com.airlock.iam.common.domain.model.state.SessionStateId;
import com.airlock.iam.common.domain.model.state.StateTimeout;
import com.airlock.iam.common.domain.model.state.UpdateAndUnlockResult;
import com.airlock.iam.common.domain.service.session.CorrelationIdService;
import com.airlock.iam.common.domain.service.session.IamSessionRepository;
import com.airlock.iam.common.domain.service.state.StateRepository;
import com.airlock.iam.common.domain.service.state.StateRepositoryKey;
import com.airlock.iam.common.domain.service.state.StateRepositoryKey.StandardStateEntryType;
import com.airlock.iam.common.infrastructure.exceptions.DeserializationFailedException;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

public final class AdminappSessionRepository implements IamSessionRepository<AdminappSession> {
   private static final ELogger LOG = ELoggerFactory.getLogger(AdminappSessionRepository.class);
   private final Provider<StateRepository> stateRepository;
   private final Provider<SessionTimeoutSettings> sessionSettings;
   private final Provider<AdminappSessionSerializer> serializer;
   private final Provider<CorrelationIdService> correlationIdService;

   @Inject
   public AdminappSessionRepository(
      Provider<StateRepository> stateRepository,
      Provider<SessionTimeoutSettings> sessionSettings,
      Provider<AdminappSessionSerializer> serializer,
      Provider<CorrelationIdService> correlationIdService
   ) {
      this.stateRepository = stateRepository;
      this.sessionSettings = sessionSettings;
      this.serializer = serializer;
      this.correlationIdService = correlationIdService;
   }

   public AdminappSession createNewSession() {
      return new AdminappSession(
         ((CorrelationIdService)this.correlationIdService.get()).determineNewSessionId(),
         SessionStateId.generateStateRepositoryKey(),
         SessionNonces.newSessionNonces()
      );
   }

   public Optional<AdminappSession> retrieveSession(SessionStateId sessionStateId) {
      FindResult findResult = ((StateRepository)this.stateRepository.get()).find(this.keyForSessionId(sessionStateId));
      if (findResult.isLocked()) {
         LOG.info("Could not acquire lock for Adminapp session with key " + this.keyForSessionId(sessionStateId));
      }

      return findResult.value()
         .map(
            serializedSession -> (AdminappSession)((AdminappSessionSerializer)this.serializer.get())
               .deserializeSession(sessionStateId, serializedSession)
               .mapResult(session -> {
                  session.setNotFresh();
                  return session;
               })
               .orElseThrow(failure -> {
                  ((StateRepository)this.stateRepository.get()).removeAndForceUnlock(this.keyForSessionId(sessionStateId));
                  throw new DeserializationFailedException(failure.type(), failure.message());
               })
         );
   }

   public void updateSession(AdminappSession session) {
      if (!session.isAuthenticated()) {
         this.removeSession(session.sessionStateId());
      } else {
         UserId userId = session.getAuthenticatedAdminIdentity().orElseThrow().id();
         String sessionJson = ((AdminappSessionSerializer)this.serializer.get()).serializeSession(session);
         boolean success;
         if (session.isFresh()) {
            InsertResult result = ((StateRepository)this.stateRepository.get())
               .insert(
                  this.keyForSessionId(session.sessionStateId()),
                  sessionJson,
                  StateTimeout.timeout(
                     ((SessionTimeoutSettings)this.sessionSettings.get()).idleTimeout(), ((SessionTimeoutSettings)this.sessionSettings.get()).lifetime()
                  ),
                  new StateRepositoryKey[0]
               );
            success = result.valueWasInserted();
         } else {
            LockResult lockResult = ((StateRepository)this.stateRepository.get()).lock(this.keyForSessionId(session.sessionStateId()));
            if (lockResult.lockAcquired()) {
               UpdateAndUnlockResult result = ((StateRepository)this.stateRepository.get())
                  .updateAndUnlock(
                     this.keyForSessionId(session.sessionStateId()),
                     sessionJson,
                     StateTimeout.timeout(
                        ((SessionTimeoutSettings)this.sessionSettings.get()).idleTimeout(), ((SessionTimeoutSettings)this.sessionSettings.get()).lifetime()
                     ),
                     new StateRepositoryKey[0]
                  );
               success = result.valueWasUpdated();
            } else {
               success = false;
            }
         }

         if (success) {
            LOG.debug("Storing Adminapp session in state repository for user " + LogUtil.logSafeQuote(userId));
         } else {
            throw new FailureException("Error while updating Adminapp session for user " + LogUtil.logSafeQuote(userId));
         }
      }
   }

   public void removeSession(SessionStateId sessionStateId) {
      LOG.debug("Removing Adminapp session from state repository");
      ((StateRepository)this.stateRepository.get()).remove(this.keyForSessionId(sessionStateId));
   }

   public void unlockSession(SessionStateId sessionStateId) {
   }

   public Optional<ReadOnlyIamSessionData> retrieveReadOnlySessionData(SessionStateId sessionStateId) {
      throw new UnsupportedOperationException("Adminapp does not support retrieving read-only session data");
   }

   private StateRepositoryKey keyForSessionId(SessionStateId sessionStateId) {
      return new StateRepositoryKey(sessionStateId.id(), StandardStateEntryType.SESSION);
   }
}
