package com.airlock.iam.authentication.infrastructure.factory;

import com.airlock.iam.authentication.api.domain.model.password.UsernamePasswordAuthenticationStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.authentication.application.configuration.fido.FidoAuthenticationStepActions;
import com.airlock.iam.authentication.application.configuration.fido.FidoAuthenticationStepErrors;
import com.airlock.iam.authentication.application.configuration.password.UsernamePasswordFidoStepConfig;
import com.airlock.iam.authentication.domain.model.password.ConditionalFidoSettings;
import com.airlock.iam.authentication.domain.model.password.PasswordAttributeName;
import com.airlock.iam.authentication.domain.model.password.UsernamePasswordFidoStep;
import com.airlock.iam.authentication.domain.model.password.UsernamePasswordFidoStepSession;
import com.airlock.iam.authentication.domain.service.password.PasswordAuthenticationStepResultFactory;
import com.airlock.iam.authentication.domain.service.password.PasswordAuthenticationStepResultFactoryImpl;
import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.common.domain.model.password.repository.UserPasswordRepository;
import com.airlock.iam.common.domain.service.password.UsernamePasswordAuthenticationService;
import com.airlock.iam.common.domain.service.password.UsernamePasswordAuthenticationServiceImpl;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactor;
import com.airlock.iam.core.misc.impl.authen.AuthenticationMethodFactory;
import com.airlock.iam.factor.application.configuration.fido.FidoRepositoryConfig;
import com.airlock.iam.factor.application.configuration.fido.FidoSettingsConfig;
import com.airlock.iam.factor.domain.model.fido.FidoChallengeGenerator;
import com.airlock.iam.factor.domain.model.fido.FidoCredentialAuthenticationSettings;
import com.airlock.iam.factor.domain.model.fido.FidoCredentialRepository;
import com.airlock.iam.factor.domain.model.fido.FidoOrigin;
import com.airlock.iam.factor.domain.model.fido.FidoRandomChallengeGenerator;
import com.airlock.iam.factor.domain.model.fido.FidoRelyingParty;
import com.airlock.iam.factor.domain.model.fido.FidoRelyingPartyName;
import com.airlock.iam.factor.domain.model.fido.FidoTimeout;
import com.airlock.iam.factor.domain.model.fido.FidoUserRepository;
import com.airlock.iam.factor.domain.model.fido.registration.FidoNewAttestationObjectFactory;
import com.airlock.iam.factor.domain.model.fido.registration.FidoNewAttestationObjectFactoryImpl;
import com.airlock.iam.factor.domain.service.fido.FidoAuthenticationService;
import com.airlock.iam.factor.infrastructure.repository.fido.FidoJooqConfigFactory;
import com.airlock.iam.factor.infrastructure.repository.fido.FidoRepositorySettings;
import com.airlock.iam.factor.infrastructure.service.fido.FidoAuthenticationServiceImpl;
import com.airlock.iam.factor.infrastructure.service.fido.FidoCredentialRepositoryImpl;
import com.airlock.iam.factor.infrastructure.service.fido.FidoUserRepositoryImpl;
import com.airlock.iam.factor.infrastructure.service.fido.YubicoCredentialRepository;
import com.airlock.iam.factor.infrastructure.service.fido.YubicoRelyingParty;
import com.airlock.iam.fido.api.domain.model.FidoRelyingPartyId;
import com.airlock.iam.flow.api.application.configuration.redflag.RedFlagConfig;
import com.airlock.iam.flow.api.domain.model.redflag.RedFlag;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
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.infrastructure.step.StepSessionModuleHelper;
import com.airlock.iam.flow.shared.api.domain.model.e2ee.E2eEncryptionStepSession;
import com.airlock.iam.flow.shared.domain.model.fido.FidoCheckingStep;
import com.airlock.iam.flow.shared.domain.model.fido.FidoLoginLogger;
import com.airlock.iam.flow.shared.domain.model.fido.FidoLoginLoggerImpl;
import com.airlock.iam.security.util.crypto.nonce.SecureRandomNonceGenerator;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.yubico.webauthn.CredentialRepository;
import java.util.Optional;
import java.util.Set;

@ModuleForConfig(UsernamePasswordFidoStepConfig.class)
public final class UsernamePasswordFidoStepModule extends PrivateModule {
   @Exposed
   @Provides
   UsernamePasswordAuthenticationServiceImpl.UsernamePasswordAuthSettings provideUsernamePasswordAuthSettings(UsernamePasswordFidoStepConfig config) {
      return UsernamePasswordAuthenticationServiceImpl.UsernamePasswordAuthSettings.builder().policyToCheckOnLogin(config.getPolicyToCheckOnLogin()).build();
   }

   @Exposed
   @Provides
   E2eEncryptionStepSession provideE2EStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.retrieveStepSession(flowSession, UsernamePasswordFidoStepSession.class);
   }

   @Exposed
   @Provides
   Optional<UsernamePasswordFidoStepSession> provideStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.tryRetrieveStepSession(flowSession, UsernamePasswordFidoStepSession.class);
   }

   @Provides
   UserPasswordRepository provideUserPasswordRepository(UsernamePasswordFidoStepConfig config, PluginConfigToLogic toLogic) {
      return toLogic.getLogic(config.getPasswordRepository());
   }

   @Exposed
   @Provides
   Optional<RedFlag> provideRedFlag(UsernamePasswordFidoStepConfig config) {
      return config.getPasswordChangeRedFlag().map(RedFlagConfig::toRedFlag);
   }

   @Exposed
   @Provides
   Optional<PasswordAttributeName> providePasswordAttributeName(UsernamePasswordFidoStepConfig config) {
      return config.getPasswordAttributeName();
   }

   @Exposed
   @Provides
   ConditionalFidoSettings provideConditionalFidoSettings(UsernamePasswordFidoStepConfig config) {
      return ConditionalFidoSettings.builder()
         .primaryTagsOnSuccess(config.getTagsOnPasswordAuthSuccess())
         .tagsOnFidoAuthSuccess(config.getTagsOnFidoAuthSuccess())
         .defaultAuthMethod(AuthenticationMethodFactory.authMethodFor(config.getAuthenticationMethodId()))
         .build();
   }

   @Exposed
   @Provides
   AuthenticationStepResultFactory provideAuthenticationStepResultFactory() {
      return AuthenticationStepResultFactory.forFactor(KnownAuthenticationFactor.PASSWORD);
   }

   @Provides
   FidoSettingsConfig provideFidoSettingsConfig(UsernamePasswordFidoStepConfig stepConfig) {
      return stepConfig.getFidoSettings();
   }

   @Provides
   FidoCredentialAuthenticationSettings provideFidoAuthenticationSettings(FidoSettingsConfig fidoConfig) {
      return FidoCredentialAuthenticationSettings.builder()
         .authenticationTimeout(FidoTimeout.timeout(fidoConfig.getAuthenticationTimeout()))
         .userVerification(fidoConfig.getAuthenticationUserVerificationPreference())
         .transports(fidoConfig.getAllowedAuthenticationTransports())
         .build();
   }

   @Provides
   @Exposed
   FidoRelyingPartyId provideFidoRelyingPartyId(FidoSettingsConfig fidoConfig) {
      return FidoRelyingPartyId.relyingPartyId(fidoConfig.getRelyingPartyId());
   }

   @Provides
   @Exposed
   FidoRelyingPartyName provideFidoRelyingPartyName(FidoSettingsConfig fidoConfig) {
      return FidoRelyingPartyName.relyingPartyName(fidoConfig.getRelyingPartyName());
   }

   @Provides
   Set<FidoOrigin> provideFidoOrigins(FidoSettingsConfig fidoConfig) {
      return fidoConfig.getAllOrigins();
   }

   @Provides
   FidoRepositoryConfig provideFidoRepositoryConfig(FidoSettingsConfig fidoConfig) {
      return fidoConfig.getRepository();
   }

   @Provides
   FidoRepositorySettings provideFidoRepositorySettings(FidoRepositoryConfig repositoryConfig) {
      return new FidoRepositorySettings(FidoJooqConfigFactory.createJooqConfig(repositoryConfig), repositoryConfig.isPersistTransports());
   }

   @Provides
   FidoChallengeGenerator provideFidoChallengeGenerator() {
      return new FidoRandomChallengeGenerator(new SecureRandomNonceGenerator());
   }

   @Provides
   FidoNewAttestationObjectFactory provideFidoAttestationObjectFactory(FidoSettingsConfig fidoConfig) {
      return FidoNewAttestationObjectFactoryImpl.fidoNewAttestationObjectFactory(fidoConfig.getMaximumAttestationObjectSizeInBytes());
   }

   @Exposed
   @Provides
   StepError provideStepError() {
      return UsernamePasswordAuthenticationStep.UsernamePasswordStepErrors.USERNAME_PASSWORD_WRONG;
   }

   @Exposed
   @Provides
   StepAction provideStepAction() {
      return UsernamePasswordFidoStep.UsernamePasswordFidoStepActions.PASSWORD_OR_FIDO_REQUIRED;
   }

   @Provides
   @Exposed
   FidoCheckingStep.FidoStepActions provideFidoStepActions() {
      return FidoAuthenticationStepActions.FIDO_AUTHENTICATION_STEP_ACTIONS;
   }

   @Provides
   @Exposed
   FidoCheckingStep.FidoStepErrors provideFidoStepErrors() {
      return FidoAuthenticationStepErrors.FIDO_AUTHENTICATION_STEP_ERRORS;
   }

   protected void configure() {
      this.bind(UsernamePasswordAuthenticationService.class).to(UsernamePasswordAuthenticationServiceImpl.class);
      this.expose(UsernamePasswordAuthenticationService.class);
      this.bind(PasswordAuthenticationStepResultFactory.class).to(PasswordAuthenticationStepResultFactoryImpl.class);
      this.expose(PasswordAuthenticationStepResultFactory.class);
      this.bind(FidoUserRepository.class).to(FidoUserRepositoryImpl.class);
      this.expose(FidoUserRepository.class);
      this.bind(FidoCredentialRepository.class).to(FidoCredentialRepositoryImpl.class);
      this.bind(CredentialRepository.class).to(YubicoCredentialRepository.class);
      this.bind(FidoRelyingParty.class).to(YubicoRelyingParty.class);
      this.bind(FidoAuthenticationService.class).to(FidoAuthenticationServiceImpl.class);
      this.expose(FidoAuthenticationService.class);
      this.bind(FidoLoginLogger.class).to(FidoLoginLoggerImpl.class);
      this.expose(FidoLoginLogger.class);
   }
}
