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

import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.core.domain.model.authentication.method.KnownAuthenticationMethods;
import com.airlock.iam.core.misc.authen.AuthenticationSession;
import com.airlock.iam.core.misc.authen.AuthenticatorException;
import com.airlock.iam.core.misc.authen.OtpCheckAuthenticator;
import com.airlock.iam.core.misc.authen.compatibility.AuthPluginContextValue;
import com.airlock.iam.core.misc.authen.compatibility.LegacyAuthPluginContextValue;
import com.airlock.iam.core.misc.authen.credential.Credential;
import com.airlock.iam.core.misc.authen.result.AuthenticationResult;
import com.airlock.iam.core.misc.impl.authen.RadiusAuthenticator;
import com.airlock.iam.core.misc.impl.authen.radius.AccessChallengeRule;
import com.airlock.iam.core.misc.impl.authen.radius.AccessRejectRule;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.infrastructure.step.StepSessionModuleHelper;
import com.airlock.iam.flow.shared.application.configuration.radiusotp.OtpCheckAccessChallengeRule;
import com.airlock.iam.flow.shared.application.configuration.radiusotp.OtpCheckAccessRejectRule;
import com.airlock.iam.flow.shared.application.configuration.radiusotp.RadiusOtpAuthStepConfig;
import com.airlock.iam.flow.shared.domain.model.otp.OtpCheckStepSession;
import com.airlock.iam.flow.shared.domain.model.otp.OtpCheckingAdapter;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@ModuleForConfig(RadiusOtpAuthStepConfig.class)
public class RadiusOtpAuthenticationStepModule extends PrivateModule {
   @Provides
   RadiusAuthenticator provideRadiusAuthenticator(RadiusOtpAuthStepConfig config) {
      return ((RadiusAuthenticator.RadiusAuthenticatorBuilder)PluginPropertyBuilderInitializer.builder(RadiusAuthenticator.RadiusAuthenticatorBuilder.class))
         .accessChallengeRules(this.toAccessChallengeRules(config.getAccessChallengeRules()))
         .accessRejectRules(this.toAccessRejectRules(config.getAccessRejectRules()))
         .encoding(config.getEncoding())
         .ifCredentialIsMissing(RadiusAuthenticator.CredentialMissing.ASK_FOR_TOKEN_IN_FIRST_STEP)
         .logRadiusAttributes(config.isLogRadiusAttributes())
         .nasIdentifier(config.getNasIdentifier())
         .passwordAndTokenUsage(RadiusAuthenticator.PasswordAndTokenUsage.TOKEN_ONLY)
         .radiusServers(config.getRadiusServers())
         .reportedAuthMethod(KnownAuthenticationMethods.valueOf(config.getReportedAuthMethod()))
         .usernameTransformers(config.getUsernameTransformers())
         .build();
   }

   private List<AccessChallengeRule> toAccessChallengeRules(List<OtpCheckAccessChallengeRule> otpAccessChallengeRules) {
      return otpAccessChallengeRules.stream().map(OtpCheckAccessChallengeRule::toAccessChallengeRule).collect(Collectors.toList());
   }

   private List<AccessRejectRule> toAccessRejectRules(List<OtpCheckAccessRejectRule> otpAccessRejectRules) {
      return otpAccessRejectRules.stream().map(OtpCheckAccessRejectRule::toAccessRejectRule).collect(Collectors.toList());
   }

   @Provides
   OtpCheckAuthenticator provideOtpCheckAuthenticator(final RadiusAuthenticator radiusAuthenticator) {
      return new OtpCheckAuthenticator() {
         @Override
         public AuthPluginContextValue authPluginClassContextValue() {
            return LegacyAuthPluginContextValue.LOGIN_REST_OTP_CHECK_AUTHENTICATOR;
         }

         @Override
         public AuthenticationSession getAuthenticationSession() {
            return radiusAuthenticator.getAuthenticationSession();
         }

         @Override
         public AuthenticationResult getNextResult(AuthenticationSession session, Credential credentials, Optional<PersistentUser> userData) throws AuthenticatorException {
            return radiusAuthenticator.getNextResult(session, credentials, userData);
         }
      };
   }

   @Exposed
   @Provides
   OtpCheckStepSession provideOtpCheckStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.retrieveStepSession(flowSession, OtpCheckStepSession.class);
   }

   @Provides
   @Exposed
   AuthenticationMethod provideAuthenticationMethod(RadiusOtpAuthStepConfig config) {
      return KnownAuthenticationMethods.valueOf(config.getReportedAuthMethod());
   }

   protected void configure() {
      this.bind(OtpCheckingAdapter.class);
      this.expose(OtpCheckingAdapter.class);
   }
}
