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

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationMethodVerifyingFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactor;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.shared.application.configuration.step.FailureStepConfig;
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.Optional;
import lombok.NonNull;

@LogicForConfig(FailureStepConfig.class)
public final class FailureStep implements AuthenticationMethodVerifyingFlowStep {
   private final Provider<StepError> configuredStepError;
   private final Provider<AuthenticationMethod> authMethod;
   private final Provider<PrincipalInfo> user;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   FailureStep(
      @NonNull Provider<StepError> configuredStepError,
      @NonNull Provider<AuthenticationMethod> authMethod,
      @NonNull Provider<PrincipalInfo> user,
      @NonNull SigningLogger signingLogger,
      @NonNull HelpdeskLogger helpdeskLogger
   ) {
      if (configuredStepError == null) {
         throw new NullPointerException("configuredStepError is marked non-null but is null");
      }

      if (authMethod == null) {
         throw new NullPointerException("authMethod is marked non-null but is null");
      }

      if (user == null) {
         throw new NullPointerException("user is marked non-null but is null");
      }

      if (signingLogger == null) {
         throw new NullPointerException("signingLogger is marked non-null but is null");
      }

      if (helpdeskLogger == null) {
         throw new NullPointerException("helpdeskLogger is marked non-null but is null");
      }

      this.configuredStepError = configuredStepError;
      this.authMethod = authMethod;
      this.user = user;
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
   }

   @Override
   public AuthenticationStepResult initialize() {
      this.logFlowAborted();
      return this.stepResultFactory().fail((StepError)this.configuredStepError.get()).build();
   }

   private void logFlowAborted() {
      String message = String.format(
         "Flow intentionally aborted for user '%s' due to the following reason: %s", this.displayUserId(), ((StepError)this.configuredStepError.get()).name()
      );
      this.signingLogger.log(message);
      this.userId().ifPresent(id -> this.helpdeskLogger.log(id, message));
   }

   private Optional<UserId> userId() {
      return ((PrincipalInfo)this.user.get()).getKnownIdentity().map(UserIdentity::id);
   }

   private String displayUserId() {
      return this.userId().map(UserId::toString).orElse("<unknown>");
   }

   @Override
   public String getDescription() {
      return "Failure Step";
   }

   @Override
   public AuthenticationMethod getAuthenticationMethod() {
      return (AuthenticationMethod)this.authMethod.get();
   }

   public AuthenticationStepResultFactory stepResultFactory() {
      return AuthenticationStepResultFactory.forFactor(KnownAuthenticationFactor.DENYING);
   }
}
