package com.airlock.iam.admin.infrastructure.factory.prot;

import com.airlock.iam.admin.application.configuration.fido.FidoTokenControllerConfig;
import com.airlock.iam.admin.application.service.token.fido.FidoAdminLogger;
import com.airlock.iam.admin.application.service.token.fido.FidoAdminLoggerImpl;
import com.airlock.iam.admin.application.service.token.fido.FidoUserCredentialAdminService;
import com.airlock.iam.admin.application.service.token.fido.FidoUserCredentialAdminServiceImpl;
import com.airlock.iam.admin.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.admin.domain.model.token.TokenController;
import com.airlock.iam.admin.domain.model.token.fido.FidoAuthenticationMethod;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.database.infrastructure.TenantId;
import com.airlock.iam.factor.application.configuration.fido.FidoAaguidMappingsConfig;
import com.airlock.iam.factor.application.configuration.fido.FidoCustomAaguidMappingConfig;
import com.airlock.iam.factor.application.configuration.fido.FidoDefaultAaguidMappingsConfig;
import com.airlock.iam.factor.application.configuration.fido.FidoRepositoryConfig;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorMetadata;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorMetadataRegistry;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorMetadataRegistryImpl;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorModel;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorModelId;
import com.airlock.iam.factor.domain.model.fido.FidoKnownAuthenticatorMetadata;
import com.airlock.iam.factor.domain.model.fido.FidoUserCredentialFactory;
import com.airlock.iam.factor.domain.model.fido.FidoUserCredentialFactoryImpl;
import com.airlock.iam.factor.domain.model.fido.FidoUserCredentialRepository;
import com.airlock.iam.factor.domain.model.fido.FidoAuthenticatorMetadataRegistryImpl.FidoAuthenticatorMetadataRegistryImplBuilder;
import com.airlock.iam.factor.domain.model.fido.registration.FidoExistingAttestationObjectFactory;
import com.airlock.iam.factor.domain.model.fido.registration.FidoExistingAttestationObjectFactoryImpl;
import com.airlock.iam.factor.infrastructure.repository.fido.FidoRepositorySettings;
import com.airlock.iam.factor.infrastructure.repository.fido.FidoUserCredentialRepositoryImpl;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.servlet.RequestScoped;
import jakarta.inject.Named;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class FidoAdminModule extends PrivateModule {
   @Provides
   FidoTokenControllerConfig provideFidoTokenController(TokenController.TokenControllers tokenControllers) {
      Optional<FidoTokenControllerConfig> tokenController = tokenControllers.ofId("FIDO");
      return (FidoTokenControllerConfig)ServiceConfigurationException.checkPresent(tokenController, "FIDO token controller must be configured.");
   }

   @Provides
   FidoRepositoryConfig provideRepositoryConfig(FidoTokenControllerConfig tokenControllerConfig) {
      return tokenControllerConfig.getRepository();
   }

   @Provides
   FidoRepositorySettings provideJooqConfig(FidoRepositoryConfig settings) {
      return new FidoRepositorySettings(
         new JooqConfig(
            settings.getSqlDataSource(),
            settings.getSqlDataSource().getDbType(),
            null,
            List.of(),
            settings.isLogQueries(),
            TenantId.tenantId(settings.getTenantId())
         ),
         settings.isPersistTransports()
      );
   }

   @Provides
   FidoExistingAttestationObjectFactory provideAttestationObjectFactory() {
      return FidoExistingAttestationObjectFactoryImpl.fidoExistingAttestationObjectFactory();
   }

   @Provides
   List<FidoAaguidMappingsConfig> provideFidoAaguidMappingsConfig(FidoTokenControllerConfig tokenControllerConfig) {
      return tokenControllerConfig.getAaguidMappings();
   }

   @Provides
   FidoAuthenticatorMetadataRegistry provideFidoAuthenticatorRegistry(List<FidoAaguidMappingsConfig> config) {
      FidoAuthenticatorMetadataRegistryImplBuilder builder = FidoAuthenticatorMetadataRegistryImpl.builder();
      config.forEach(c -> builder.authenticators(aaguidMappingLogic(c)));
      return builder.build();
   }

   private static List<FidoAuthenticatorMetadata> aaguidMappingLogic(FidoAaguidMappingsConfig config) {
      if (config instanceof FidoDefaultAaguidMappingsConfig) {
         return FidoKnownAuthenticatorMetadata.knownAuthenticatorMetadata();
      } else if (config instanceof FidoCustomAaguidMappingConfig singleAuthenticatorMetadataConfig) {
         return Collections.singletonList(
            FidoAuthenticatorMetadata.authenticatorMetadata(
               FidoAuthenticatorModelId.authenticatorAaguid(singleAuthenticatorMetadataConfig.getAaguid()),
               FidoAuthenticatorModel.authenticatorModel(singleAuthenticatorMetadataConfig.getDescription())
            )
         );
      } else {
         throw new IllegalStateException("Unknown type of " + config.getClass().getCanonicalName());
      }
   }

   @Provides
   @RequestScoped
   @Exposed
   @Named("FIDO")
   AuthenticationMethod provideAuthMethod(FidoTokenControllerConfig controller, FidoUserCredentialRepository repository) {
      return FidoAuthenticationMethod.builder()
         .configuredId(controller.getConfiguredId())
         .selectableAsActiveAuthMethod(controller.isSelectableAsActiveAuthMethod())
         .selectableAsNextAuthMethod(controller.isSelectableAsNextAuthMethod())
         .repository(repository)
         .build();
   }

   protected void configure() {
      this.bind(FidoUserCredentialRepository.class).to(FidoUserCredentialRepositoryImpl.class);
      this.bind(FidoUserCredentialFactory.class).to(FidoUserCredentialFactoryImpl.class);
      this.bind(FidoAdminLogger.class).to(FidoAdminLoggerImpl.class).in(RequestScoped.class);
      this.expose(FidoAdminLogger.class);
      this.bind(FidoUserCredentialAdminService.class).to(FidoUserCredentialAdminServiceImpl.class).in(RequestScoped.class);
      this.expose(FidoUserCredentialAdminService.class);
   }
}
