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

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.plugin.LogicForConfig;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.otp.UnverifiedOtp;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.core.misc.tokenverifier.TokenVerifierResponse;
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.step.StepSession;
import com.airlock.iam.flow.shared.application.configuration.oath.OathOtpActivationStepConfig;
import com.airlock.iam.flow.shared.domain.service.oath.OathOtpActivationService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

@LogicForConfig(OathOtpActivationStepConfig.class)
public class OathOtpActivationStepImpl implements OathOtpActivationStep, AuthenticationFlowStep {
   private static final String IMAGE_ATTRIBUTE = "activationImage";
   private static final String CODE_ATTRIBUTE = "activationCode";
   private final Provider<AuthenticationMethod> authMethod;
   private final Provider<OathOtpActivationService> oathOtpService;

   @Inject
   public OathOtpActivationStepImpl(Provider<AuthenticationMethod> authMethod, Provider<OathOtpActivationService> oathOtpService) {
      this.authMethod = authMethod;
      this.oathOtpService = oathOtpService;
   }

   @Override
   public AuthenticationStepResult initialize() {
      Result<OathOtpActivationService.OathActivationData, OathOtpActivationService.ActivationError> result = ((OathOtpActivationService)this.oathOtpService
            .get())
         .createActivationData();
      return result.isFailure()
         ? this.stepResultFactory().fail(OathOtpActivationStepImpl.OathOtpErrors.OATH_OTP_ACTIVATION_FAILED).build()
         : addAdditionalAttributes(this.stepResultFactory().stay(OathOtpActivationStepImpl.OathOtpStepActions.OATH_OTP_ACTIVATION_REQUIRED), result.get())
            .build();
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(new OathOtpActivationStepSession());
   }

   @Override
   public String getDescription() {
      return "Activates an OATH OTP token.";
   }

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

   public AuthenticationStepResultFactory stepResultFactory() {
      return AuthenticationStepResultFactory.forFactorDetail(KnownAuthenticationFactorDetail.OTP_OATH);
   }

   public AuthenticationStepResult complete(UnverifiedOtp unverifiedOtp) {
      TokenVerifierResponse verifierResponse = ((OathOtpActivationService)this.oathOtpService.get()).verifyToken(unverifiedOtp.rawValue());

      return switch (verifierResponse) {
         case TOKEN_ACCEPTED -> this.stepResultFactory().success().build();
         case TOKEN_DENIED -> this.stepResultFactory()
            .failRetry(OathOtpActivationStepImpl.OathOtpErrors.OATH_OTP_WRONG, OathOtpActivationStepImpl.OathOtpStepActions.OATH_OTP_ACTIVATION_REQUIRED)
            .build();
         default -> this.stepResultFactory().fail(OathOtpActivationStepImpl.OathOtpErrors.OATH_OTP_ACTIVATION_FAILED).build();
      };
   }

   private static AuthenticationStepResultBuilder addAdditionalAttributes(
      AuthenticationStepResultBuilder builder, OathOtpActivationService.OathActivationData result
   ) {
      builder.addAdditionalAttribute("activationImage", result.asEncodedQrCode());
      builder.addAdditionalAttribute("activationCode", result.sharedSecret());
      return builder;
   }

   public enum OathOtpErrors implements StepError {
      OATH_OTP_ACTIVATION_FAILED,
      OATH_OTP_WRONG;
   }

   public enum OathOtpStepActions implements StepAction {
      OATH_OTP_ACTIVATION_REQUIRED;
   }
}
