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

import com.airlock.iam.authentication.api.domain.model.password.UsernamePasswordAuthenticationStep;
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.AuthenticationStepResultFactory;
import com.airlock.iam.authentication.api.domain.model.step.IdentityProvidingFlowStep;
import com.airlock.iam.authentication.application.configuration.password.HttpBasicAuthenticationStepConfig;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.domain.service.credential.HttpBasicAuthExtractor;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepResultData;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.domain.model.result.NotAuthorizedData;
import com.airlock.iam.flow.shared.domain.model.step.access.AccessFlowStep;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

@LogicForConfig(HttpBasicAuthenticationStepConfig.class)
public final class BasicAuthStep implements IdentityProvidingFlowStep, AuthenticationFlowStep, AccessFlowStep {
   private final Provider<UsernamePasswordAuthenticationStep> passwordStep;
   private final Provider<AuthenticationMethod> authMethod;
   private final Provider<HttpBasicAuthenticationStepConfig> config;
   private final Provider<BasicAuthExtractor> basicAuthExtractor;

   @Inject
   public BasicAuthStep(
      Provider<UsernamePasswordAuthenticationStep> passwordStep,
      Provider<AuthenticationMethod> authMethod,
      Provider<HttpBasicAuthenticationStepConfig> config,
      Provider<BasicAuthExtractor> basicAuthExtractor
   ) {
      this.passwordStep = passwordStep;
      this.authMethod = authMethod;
      this.config = config;
      this.basicAuthExtractor = basicAuthExtractor;
   }

   @Override
   public AuthenticationStepResult initialize() {
      ((UsernamePasswordAuthenticationStep)this.passwordStep.get()).initialize();
      return this.stepResultFactory().stay(AccessFlowStep.AccessStepActions.ACCESS_CALL_REQUIRED).build();
   }

   public AuthenticationStepResult access() {
      Optional<HttpBasicAuthExtractor.BasicAuthHeader> basicAuthHeader = ((BasicAuthExtractor)this.basicAuthExtractor.get())
         .extractBasicAuthHeader(((HttpBasicAuthenticationStepConfig)this.config.get()).getCharset());
      if (basicAuthHeader.isEmpty()) {
         return this.stepResultFactory()
            .failRetry(GenericStepError.NOT_AUTHORIZED, AccessFlowStep.AccessStepActions.ACCESS_CALL_REQUIRED)
            .resultData(this.notAuthorized())
            .isFailedFactor(false)
            .build();
      }

      AuthenticationStepResult result = ((UsernamePasswordAuthenticationStep)this.passwordStep.get())
         .checkPassword(basicAuthHeader.get().username().getValue(), basicAuthHeader.get().password());
      return result.hasError(UsernamePasswordAuthenticationStep.UsernamePasswordStepErrors.USERNAME_PASSWORD_WRONG)
         ? this.stepResultFactory()
            .failRetry(GenericStepError.NOT_AUTHORIZED, AccessFlowStep.AccessStepActions.ACCESS_CALL_REQUIRED)
            .resultData(this.notAuthorized())
            .build()
         : result;
   }

   private StepResultData notAuthorized() {
      return NotAuthorizedData.basicAuthentication(((HttpBasicAuthenticationStepConfig)this.config.get()).getRealm());
   }

   @Override
   public Optional<StepSession> createSession() {
      return ((UsernamePasswordAuthenticationStep)this.passwordStep.get()).createSession();
   }

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

   @Override
   public String getDescription() {
      return "HTTP Basic Authentication Step";
   }

   public AuthenticationStepResultFactory stepResultFactory() {
      return (AuthenticationStepResultFactory)((UsernamePasswordAuthenticationStep)this.passwordStep.get()).stepResultFactory();
   }
}
