package com.airlock.iam.common.domain.model.principal;

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.authentication.failed.FailedLoginsByAuthMethod;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.domain.model.event.UserEventFactory;
import com.airlock.iam.common.domain.model.user.Lockable;
import com.airlock.iam.common.domain.service.token.UserSessionTokenService;
import com.airlock.iam.core.misc.authen.TooManyAuthenticationAttemptsLockReason;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.OptionalInt;

public final class MaxFailedAttemptsImpl implements MaxFailedAttempts {
   private final int maxFailedAttempts;
   private final HelpdeskLogger helpdeskLogger;
   private final SigningLogger signingLogger;
   private final UserEventFactory eventFactory;
   private final Provider<UserSessionTokenService> userSessionTokenService;

   @Inject
   public MaxFailedAttemptsImpl(
      int maxFailedAttempts,
      HelpdeskLogger helpdeskLogger,
      SigningLogger signingLogger,
      UserEventFactory eventFactory,
      Provider<UserSessionTokenService> userSessionTokenService
   ) {
      this.maxFailedAttempts = maxFailedAttempts;
      this.helpdeskLogger = helpdeskLogger;
      this.signingLogger = signingLogger;
      this.eventFactory = eventFactory;
      this.userSessionTokenService = userSessionTokenService;
   }

   @Override
   public boolean isExceededBy(FailedLoginsByAuthMethod failedLogins) {
      return failedLogins.isAnyExceeded(this.maxFailedAttempts);
   }

   @Override
   public void lockIfExceeded(FailedLoginsByAuthMethod failedLogins, Lockable lockable) {
      if (this.isExceededBy(failedLogins) && !lockable.isLocked()) {
         AuthenticationMethod exceededAuthMethod = failedLogins.methodsOfExceededCounters(this.maxFailedAttempts).getFirst();
         TooManyAuthenticationAttemptsLockReason lockReason = TooManyAuthenticationAttemptsLockReason.createForAuthenticationMethod(exceededAuthMethod);
         lockable.lock(lockReason);
         UserId userId = UserId.userId(lockable.id());
         ((UserSessionTokenService)this.userSessionTokenService.get()).handleUserLocked(userId);
         this.eventFactory.publishUserLockedEvent(userId, lockReason);
         this.helpdeskLogger.log(userId, "User locked because of too many failed attempts with method " + LogUtil.quote(exceededAuthMethod.getId()));
         this.signingLogger
            .log("Locked user '%s' because of too many failed attempts for counter for method '%s'.", new Object[]{lockable.id(), exceededAuthMethod.getId()});
      }
   }

   @Override
   public OptionalInt getFailedAttemptsLimit() {
      return OptionalInt.of(this.maxFailedAttempts);
   }
}
