package com.airlock.iam.login.rest.infrastructure.mapper.shared;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.common.domain.model.auth.ExternalizationContext;
import com.airlock.iam.common.infrastructure.transfer.response.ExternalizationContextData;
import com.airlock.iam.core.domain.model.cronto.challenge.CrontoDeactivationChallenge;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed;
import com.airlock.iam.core.misc.impl.token.cronto.CrontoDeviceToken;
import com.airlock.iam.core.misc.impl.token.cronto.CrontoPushNotificationStatus;
import com.airlock.iam.flow.domain.model.FlowInformation;
import com.airlock.iam.login.rest.infrastructure.transfer.FlowInformationData;
import com.airlock.iam.login.rest.infrastructure.transfer.FlowInformationType;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceActivationCompletedData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDeviceDeletionChallengeData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyCrontoDevicePushNotificationState;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.MyMtanTokenData;
import com.airlock.iam.login.rest.infrastructure.transfer.shared.response.my.UserData;
import ma.glasnost.orika.CustomMapper;
import ma.glasnost.orika.DefaultFieldMapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;

public final class LoginRestBeanMappings {
   public LoginRestBeanMappings(MapperFactory factory) {
      this.configureAll(factory);
   }

   private void configureAll(MapperFactory factory) {
      this.configureMyMtanTokenDataForMtanChoice(factory);
      this.configureMyCrontoDeviceData(factory);
      this.configureMyCrontoDeviceActivationCompletedData(factory);
      this.configureExternalizationContextData(factory);
      this.configureUserData(factory);
      this.configureDeactivationChallenge(factory);
      this.configureFlowInformationData(factory);
   }

   private void configureMyMtanTokenDataForMtanChoice(MapperFactory factory) {
      factory.classMap(AuthenticationFailed.MtanChoiceToken.class, MyMtanTokenData.class)
         .field("maskedPhoneNumber", "number")
         .field("preSelected", "defaultNumber")
         .byDefault(new DefaultFieldMapper[0])
         .register();
   }

   private void configureDeactivationChallenge(MapperFactory factory) {
      factory.classMap(CrontoDeactivationChallenge.class, MyCrontoDeviceDeletionChallengeData.class)
         .customize(new CustomMapper<CrontoDeactivationChallenge, MyCrontoDeviceDeletionChallengeData>() {
            public void mapAtoB(CrontoDeactivationChallenge challenge, MyCrontoDeviceDeletionChallengeData data, MappingContext context) {
               data.setChallengeImage(challenge.getCryptogramForScreen().base64image());
               challenge.getRawChallengeString().ifPresent(data::setSecureChannelChallenge);
            }
         })
         .register();
   }

   private void configureMyCrontoDeviceData(MapperFactory factory) {
      factory.classMap(CrontoDeviceToken.class, MyCrontoDeviceData.class)
         .byDefault(new DefaultFieldMapper[0])
         .customize(
            new CustomMapper<CrontoDeviceToken, MyCrontoDeviceData>() {
               public void mapAtoB(CrontoDeviceToken crontoDeviceToken, MyCrontoDeviceData myCrontoDeviceData, MappingContext context) {
                  myCrontoDeviceData.setDisplayId(crontoDeviceToken.getDeviceIdForDisplay());
                  myCrontoDeviceData.setLabel(crontoDeviceToken.getCustomName());
                  myCrontoDeviceData.setActivationDate(crontoDeviceToken.getActivationDate());
                  myCrontoDeviceData.setFirstUsageDate(crontoDeviceToken.getFirstUsageDate());
                  myCrontoDeviceData.setLastUsageDate(crontoDeviceToken.getLatestUsageDate());
                  myCrontoDeviceData.setEnabled(crontoDeviceToken.isActive());
                  myCrontoDeviceData.setPushNotificationState(
                     new MyCrontoDevicePushNotificationState(
                        crontoDeviceToken.getPushNotificationStatus().getStatus(),
                        crontoDeviceToken.isPushNotificationAllowed(),
                        crontoDeviceToken.getPushNotificationStatus().equals(CrontoPushNotificationStatus.ENABLED)
                     )
                  );
                  if (crontoDeviceToken.getPlatformType().isPresent()) {
                     myCrontoDeviceData.setPlatformType(crontoDeviceToken.getPlatformType().get().getIdentifier());
                  }
               }
            }
         )
         .register();
   }

   private void configureMyCrontoDeviceActivationCompletedData(MapperFactory factory) {
      factory.classMap(CrontoDeviceToken.class, MyCrontoDeviceActivationCompletedData.class)
         .byDefault(new DefaultFieldMapper[0])
         .customize(
            new CustomMapper<CrontoDeviceToken, MyCrontoDeviceActivationCompletedData>() {
               public void mapAtoB(CrontoDeviceToken crontoDeviceToken, MyCrontoDeviceActivationCompletedData myCrontoDeviceData, MappingContext context) {
                  myCrontoDeviceData.setDisplayId(crontoDeviceToken.getDeviceIdForDisplay());
                  myCrontoDeviceData.setLabel(crontoDeviceToken.getCustomName());
                  myCrontoDeviceData.setActivationDate(crontoDeviceToken.getActivationDate());
                  myCrontoDeviceData.setFirstUsageDate(crontoDeviceToken.getFirstUsageDate());
                  myCrontoDeviceData.setLastUsageDate(crontoDeviceToken.getLatestUsageDate());
                  myCrontoDeviceData.setEnabled(crontoDeviceToken.isActive());
                  myCrontoDeviceData.setPushNotificationState(
                     new MyCrontoDevicePushNotificationState(
                        crontoDeviceToken.getPushNotificationStatus().getStatus(),
                        crontoDeviceToken.isPushNotificationAllowed(),
                        crontoDeviceToken.getPushNotificationStatus().equals(CrontoPushNotificationStatus.ENABLED)
                     )
                  );
                  if (crontoDeviceToken.getPlatformType().isPresent()) {
                     myCrontoDeviceData.setPlatformType(crontoDeviceToken.getPlatformType().get().getIdentifier());
                  }
               }
            }
         )
         .register();
   }

   private void configureExternalizationContextData(MapperFactory factory) {
      factory.classMap(ExternalizationContext.class, ExternalizationContextData.class).fieldMap("jwt", "context").add().register();
   }

   private void configureUserData(MapperFactory factory) {
      factory.classMap(PersistentUser.class, UserData.class)
         .field("latestSuccessfulLogin", "latestSuccessfulAuthentication")
         .field("secondLatestSuccessfulLogin", "secondLatestSuccessfulAuthentication")
         .register();
   }

   private void configureFlowInformationData(MapperFactory factory) {
      factory.classMap(FlowInformation.class, FlowInformationData.class).customize(new CustomMapper<FlowInformation, FlowInformationData>() {
         public void mapAtoB(FlowInformation flowInformation, FlowInformationData flowInformationData, MappingContext context) {
            FlowInformationType flowInformationType = LoginRestBeanMappings.this.getFlowInformationType(flowInformation);
            flowInformationData.setFlowType(flowInformationType);
            flowInformationData.setNextStep(flowInformation.getNextStep().name());
         }
      }).register();
   }

   private FlowInformationType getFlowInformationType(FlowInformation flowInformation) {
      switch (flowInformation.getFlowType().name()) {
         case "AUTHENTICATION_AND_AUTHORIZATION":
            return FlowInformationType.AUTHENTICATION;
         case "PUBLIC_SELF_SERVICE":
            return FlowInformationType.PUBLIC_SELF_SERVICE;
         case "SELF_SERVICE":
            return FlowInformationType.PROTECTED_SELF_SERVICE;
         case "TECH_CLIENT_REG":
            return FlowInformationType.TECH_CLIENT_REGISTRATION;
         case "USER_SELF_REG":
            return FlowInformationType.USER_SELF_REGISTRATION;
         default:
            throw new FailureException("No mapping for unknown flow type");
      }
   }
}
