package com.airlock.iam.transactionapproval.infrastructure.mapper;

import com.airlock.iam.common.domain.model.auth.ExternalizationContext;
import com.airlock.iam.common.infrastructure.transfer.response.ExternalizationContextData;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed.MtanChoiceToken;
import com.airlock.iam.core.misc.impl.authen.cronto.CrontoPushDeviceChoice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceCapability;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.shared.domain.model.airlock2fa.Airlock2FAOfflineQrCodeChallenge;
import com.airlock.iam.flow.shared.domain.model.cronto.CrontoAuthChallenge;
import com.airlock.iam.flow.shared.domain.model.cronto.CrontoPushDevice;
import com.airlock.iam.transactionapproval.api.infrastructure.transfer.response.TransactionApprovalFlowResultData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.Airlock2FADeviceLegacyCapabilityData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalAirlock2FADeviceData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalAirlock2FAOfflineChallengeData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalCrontoChallengeData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalCrontoPushDeviceData;
import com.airlock.iam.transactionapproval.infrastructure.transfer.response.TransactionApprovalMtanTokenData;
import ma.glasnost.orika.CustomConverter;
import ma.glasnost.orika.CustomMapper;
import ma.glasnost.orika.DefaultFieldMapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.metadata.Type;
import ma.glasnost.orika.metadata.TypeFactory;

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

   private void configureAll(MapperFactory factory) {
      this.configureMtanTokenDataForMtanChoice(factory);
      this.configureCrontoChallengeData(factory);
      this.configureCrontoPushDeviceChoiceData(factory);
      this.configureFlowResultData(factory);
      this.configureExternalizationContextData(factory);
      this.configureAirlock2FAChallengeData(factory);
      this.configureAirlock2FADeviceData(factory);
   }

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

   private void configureAirlock2FAChallengeData(MapperFactory factory) {
      factory.classMap(Airlock2FAOfflineQrCodeChallenge.class, TransactionApprovalAirlock2FAOfflineChallengeData.class)
         .field("challengeImage.encodedImage", "challengeImage")
         .register();
   }

   private void configureAirlock2FADeviceData(MapperFactory factory) {
      factory.classMap(Airlock2FAAuthDevice.class, TransactionApprovalAirlock2FADeviceData.class)
         .field("displayName.value", "displayName")
         .byDefault(new DefaultFieldMapper[0])
         .customize(new CustomMapper<Airlock2FAAuthDevice, TransactionApprovalAirlock2FADeviceData>() {
            public void mapAtoB(Airlock2FAAuthDevice source, TransactionApprovalAirlock2FADeviceData target, MappingContext context) {
               if (source.getCapabilities().contains(Airlock2FADeviceCapability.TOTP)) {
                  target.getCapabilities().add(Airlock2FADeviceLegacyCapabilityData.MOBILE_TOTP);
               }
            }
         })
         .register();
   }

   private void configureCrontoChallengeData(MapperFactory factory) {
      factory.getConverterFactory().registerConverter(new TransactionApprovalRestBeanMappings.CrontoChallengeConverter());
   }

   private void configureCrontoPushDeviceChoiceData(MapperFactory factory) {
      factory.classMap(CrontoPushDeviceChoice.class, TransactionApprovalCrontoPushDeviceData.class)
         .field("preSelected", "defaultDevice")
         .field("deviceId", "id")
         .byDefault(new DefaultFieldMapper[0])
         .register();
   }

   private void configureFlowResultData(MapperFactory factory) {
      factory.classMap(FlowResult.class, TransactionApprovalFlowResultData.class).field("nextStep:{nextStep}", "nextStep").register();
   }

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

   private static class CrontoChallengeConverter extends CustomConverter<CrontoAuthChallenge, TransactionApprovalCrontoChallengeData> {
      public TransactionApprovalCrontoChallengeData convert(
         CrontoAuthChallenge source, Type<? extends TransactionApprovalCrontoChallengeData> destinationType, MappingContext mappingContext
      ) {
         return TransactionApprovalCrontoChallengeData.builder()
            .challengeImage(source.getBase64Cryptogram())
            .secureChannelChallenge(source.getSecureChannelChallenge().orElse("-"))
            .onlineValidation(source.isOnlineValidation())
            .pushed(source.isPushed())
            .pushDevices(source.getPushDevices().stream().map(TransactionApprovalRestBeanMappings.CrontoChallengeConverter::convert).toList())
            .build();
      }

      public boolean canConvert(Type<?> sourceType, Type<?> destinationType) {
         Type<Object> objectType = TypeFactory.valueOf(Object.class);
         Type<TransactionApprovalCrontoChallengeData> transactionApprovalCrontoChallengeDataType = TypeFactory.valueOf(
            TransactionApprovalCrontoChallengeData.class
         );
         return this.sourceType.equals(sourceType)
            && (destinationType.equals(objectType) || destinationType.equals(transactionApprovalCrontoChallengeDataType));
      }

      private static TransactionApprovalCrontoChallengeData.CrontoPushDeviceData convert(CrontoPushDevice crontoPushDevice) {
         return TransactionApprovalCrontoChallengeData.CrontoPushDeviceData.builder()
            .id(crontoPushDevice.id())
            .label((String)crontoPushDevice.label().orElse(null))
            .platform((String)crontoPushDevice.platform().orElse(null))
            .build();
      }
   }
}
