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

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.fido.FidoPasswordlessAuthenticationStepConfig;
import com.airlock.iam.authentication.domain.model.fido.FidoChallengeSession;
import com.airlock.iam.authentication.domain.model.fido.FidoPasswordlessAuthenticationStepSession;
import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
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.domain.model.session.FlowSession;
import com.airlock.iam.flow.infrastructure.step.StepSessionModuleHelper;
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.Set;

@ModuleForConfig(FidoPasswordlessAuthenticationStepConfig.class)
public final class FidoPasswordlessAuthenticationStepModule extends PrivateModule {
   @Exposed
   @Provides
   FidoChallengeSession provideFidoAuthenticationStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.retrieveStepSession(flowSession, FidoPasswordlessAuthenticationStepSession.class);
   }

   @Provides
   FidoSettingsConfig provideFidoSettings(FidoPasswordlessAuthenticationStepConfig 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());
   }

   @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(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);
   }
}
