package com.airlock.iam.authentication.domain.model.airlock2fa;

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultBuilder;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.api.domain.model.device.KnownDeviceTypes;
import com.airlock.iam.common.api.domain.model.device.usage.AuthenticationDeviceUsage;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthLogData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAShortLivedOnlineQrCodeSettings;
import com.airlock.iam.factor.domain.model.airlock2fa.ImmediateAuthenticationAnswer;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAuthenticationService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FALogHelper;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.shared.domain.model.airlock2fa.StepResultHelper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Provider;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;

public final class AuthenticationStepResultHelper {
   private static final String AUTHENTICATION_ACTION_DESCRIPTION = "authentication";
   private static final String FACTOR_CHECK_ACTION_DESCRIPTION = "factor check";
   private static final String LOGIN_ID_KEY = "loginId";
   private final Provider<Airlock2FAAuthenticationStepSettings> settings;
   private final Provider<Airlock2FAAuthenticationStepSession> stepSession;
   private final Provider<Airlock2FALogHelper> logHelper;
   private final Provider<TimeProvider> timeProvider;

   public AuthenticationStepResult stepResultForSuccessfulBypass() {
      ((Airlock2FALogHelper)this.logHelper.get()).logSuccessfulBypass();
      return this.stepResultFactory().success().obtainedTags(((Airlock2FAAuthenticationStepSettings)this.settings.get()).getTagsOnBypass()).build();
   }

   public AuthenticationStepResult stepResultForStatusAnswer(Airlock2FAAuthenticationService.AuthenticationStatusAnswer answer, Airlock2FAFactor factor) {
      ((Airlock2FALogHelper)this.logHelper.get()).logAuthenticationAnswerDetails(answer, this.logData());

      return switch (answer.getResult()) {
         case ALLOW -> this.factorCheckSucceeded(factor, this.logData().withUserPresenceVerificationIfPresent(answer.getUserPresenceVerification()));
         case DENY, TIMEOUT -> this.factorCheckFailed(factor, AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED);
         case WAITING -> this.nextStayStepResult(this.stepSession());
      };
   }

   public AuthenticationStepResult stepResultForZeroTouchInformation() {
      return this.stepResultFactory().stay(this.nextStayStepAction()).resultData(this.createZeroTouchInformation()).build();
   }

   public AuthenticationStepResult stepResultForSessionStartImmediateAnswer(ImmediateAuthenticationAnswer answer) {
      return switch (answer.getResult()) {
         case ALLOW -> this.failedDueToBypassMode();
         case DENY -> this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build();
      };
   }

   public boolean isSuccessfulAuthenticationResult(ImmediateAuthenticationAnswer.ImmediateAuthenticationResult authResult) {
      return switch (authResult) {
         case ALLOW -> true;
         case DENY -> false;
      };
   }

   public boolean isSuccessfulAuthenticationResult(Airlock2FAAuthenticationService.AuthenticationStatusResult authResult) {
      return switch (authResult) {
         case ALLOW -> true;
         case DENY, TIMEOUT, WAITING -> false;
      };
   }

   public boolean isDenyAuthenticationResult(Airlock2FAAuthenticationService.AuthenticationStatusResult authResult) {
      return switch (authResult) {
         case ALLOW, TIMEOUT, WAITING -> false;
         case DENY -> true;
      };
   }

   public AuthenticationStepResult failedDueToBypassMode() {
      ((Airlock2FALogHelper)this.logHelper.get()).logFailedDueToBypassMode("authentication");
      return this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build();
   }

   public AuthenticationStepResult nextStayStepResult(Airlock2FAAuthenticationStepSession session) {
      AuthenticationStepResultBuilder builder = this.stayStepResultBuilder();
      if (session.topFactorEquals(Airlock2FAFactor.ONE_TOUCH)) {
         this.loginId().ifPresent(id -> builder.addAdditionalAttribute("loginId", id));
         session.getMultiNumberedChallenge().ifPresent(value -> StepResultHelper.addMultiNumberedChallenge(builder, value));
      } else if (session.topFactorEquals(Airlock2FAFactor.ONLINE_QR_CODE)) {
         Optional<Airlock2FAShortLivedOnlineQrCodeSettings> shortLivedOnlineQrCodeSettings = ((Airlock2FAAuthenticationStepSettings)this.settings.get())
            .getShortLivedOnlineQrCodeSettings();
         if (shortLivedOnlineQrCodeSettings.isPresent()) {
            Instant currentTime = ((TimeProvider)this.timeProvider.get()).getCurrentInstant();
            if (StepResultHelper.hasSessionTimeout(session.getSessionStartTime(), currentTime, shortLivedOnlineQrCodeSettings.get().sessionTimeout())) {
               return this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build();
            }

            StepResultHelper.addAuthenticationShortLivedQrCodeChallenge(
               builder, session.getSessionExchangeTokens(), currentTime, shortLivedOnlineQrCodeSettings.get().validityOverlap()
            );
         } else if (session.getOnlineQrCodeChallenge().isPresent()) {
            StepResultHelper.addOnlineQrCodeChallenge(builder, session.getOnlineQrCodeChallenge().get());
         }
      } else if (session.topFactorEquals(Airlock2FAFactor.OFFLINE_QR_CODE) || session.topFactorEquals(Airlock2FAFactor.PASSCODE)) {
         session.getOfflineQrCodeChallenge().ifPresent(challenge -> StepResultHelper.addOfflineQrCodeChallenge(builder, challenge));
      }

      return builder.build();
   }

   private AuthenticationStepResultBuilder stayStepResultBuilder() {
      return this.stepResultFactory().stay(this.nextStayStepAction());
   }

   private Airlock2FAZeroTouchInformation createZeroTouchInformation() {
      return new Airlock2FAZeroTouchInformation(false, null, null, this.loginId().orElse(null));
   }

   public AuthenticationStepResult factorCheckSucceeded(Airlock2FAFactor factor) {
      return this.factorCheckSucceeded(factor, this.logData());
   }

   private AuthenticationStepResult factorCheckSucceeded(Airlock2FAFactor factor, Airlock2FAAuthLogData logData) {
      Set<Tag> tags = ((Airlock2FAAuthenticationStepSettings)this.settings.get()).getTagsOnSuccess().get(factor);
      ((Airlock2FALogHelper)this.logHelper.get()).logSuccess(factor, this.stepSession().getOnlyDeviceOrThrow(), "factor check", logData);
      ((Airlock2FALogHelper)this.logHelper.get())
         .log(String.format("Obtained tags (%s) due to successful %s (%s)", StringUtils.join(tags, ", "), "factor check", factor.getDisplayName()), logData);
      return this.stepResultFactory().success().deviceUsageData(this.deviceUsageData(this.stepSession().getOnlyDeviceIdOrThrow())).obtainedTags(tags).build();
   }

   private AuthenticationDeviceUsage deviceUsageData(Airlock2FADeviceId device) {
      return new AuthenticationDeviceUsage(KnownDeviceTypes.AIRLOCK_2FA, device, ((TimeProvider)this.timeProvider.get()).getCurrentInstant());
   }

   public AuthenticationStepResult factorCheckFailRetry(Airlock2FAFactor factor, StepError stepError) {
      ((Airlock2FALogHelper)this.logHelper.get()).logFailed(Optional.of(factor), this.logData());
      AuthenticationStepResultBuilder builder = this.stepResultFactory().failRetry(stepError, this.nextStayStepAction());
      if (this.stepSession().topFactorEquals(Airlock2FAFactor.OFFLINE_QR_CODE) || this.stepSession().topFactorEquals(Airlock2FAFactor.PASSCODE)) {
         this.stepSession().getOfflineQrCodeChallenge().ifPresent(challenge -> StepResultHelper.addOfflineQrCodeChallenge(builder, challenge));
      }

      return builder.build();
   }

   public AuthenticationStepResult factorCheckFailed(Airlock2FAFactor factor, StepError stepError) {
      ((Airlock2FALogHelper)this.logHelper.get()).logFailed(Optional.of(factor), this.logData());
      return this.stepResultFactory().fail(stepError).build();
   }

   private StepAction nextStayStepAction() {
      Airlock2FAFactor factor = this.stepSession().topFactor().orElseThrow();

      return switch (factor) {
         case ONE_TOUCH, ONLINE_QR_CODE -> this.stepSession().canEnableOfflineMode()
            ? Airlock2FAUserFactorAuthenticationStep.Airlock2FAUserFactorAuthenticationStepAction.AIRLOCK_2FA_POLLING_OR_OFFLINE_REQUIRED
            : Airlock2FAAuthenticationStep.Airlock2FAAuthenticationStepAction.AIRLOCK_2FA_POLLING_REQUIRED;
         case PASSCODE -> this.stepSession().getRemainingFactors().contains(Airlock2FAFactor.OFFLINE_QR_CODE)
            ? Airlock2FAUserFactorAuthenticationStep.Airlock2FAUserFactorAuthenticationStepAction.AIRLOCK_2FA_PASSCODE_OR_QR_CODE_OTP_REQUIRED
            : Airlock2FAUserFactorAuthenticationStep.Airlock2FAUserFactorAuthenticationStepAction.AIRLOCK_2FA_PASSCODE_REQUIRED;
         case OFFLINE_QR_CODE -> Airlock2FAUserFactorAuthenticationStep.Airlock2FAUserFactorAuthenticationStepAction.AIRLOCK_2FA_QR_CODE_OTP_REQUIRED;
         case MOBILE_ONLY, USERNAMELESS_QR_CODE -> throw new FailureException(factor.getDisplayName() + " is not supported by this step.");
      };
   }

   private Optional<String> loginId() {
      return ((Airlock2FAAuthenticationStepSession)this.stepSession.get()).getLoginId();
   }

   private AuthenticationStepResultFactory stepResultFactory() {
      return AuthenticationStepResultFactory.forFactorDetail(this.stepSession().authenticationFactorDetail());
   }

   private Airlock2FAAuthenticationStepSession stepSession() {
      return (Airlock2FAAuthenticationStepSession)this.stepSession.get();
   }

   private Airlock2FAAuthLogData logData() {
      return this.stepSession().getLogData();
   }

   @SuppressFBWarnings(justification = "generated code")
   public AuthenticationStepResultHelper(
      Provider<Airlock2FAAuthenticationStepSettings> settings,
      Provider<Airlock2FAAuthenticationStepSession> stepSession,
      Provider<Airlock2FALogHelper> logHelper,
      Provider<TimeProvider> timeProvider
   ) {
      this.settings = settings;
      this.stepSession = stepSession;
      this.logHelper = logHelper;
      this.timeProvider = timeProvider;
   }
}
