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

import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAActivationLetterViewer;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAAdminAppDevice;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAUserAccount;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.Airlock2FAActivationLetterData;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.Airlock2FAActivationLetterOrderData;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.Airlock2FAAppDeviceData;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.Airlock2FAConfidentialActivationData;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.Airlock2FAUserAccountData;
import com.airlock.iam.admin.infrastructure.transfer.response.user.tokens.UserAirlock2FAHardwareDeviceData;
import com.airlock.iam.common.infrastructure.transfer.response.Airlock2FAAppDeviceFactorData;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactor;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAFactorSet;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAHardwareTokenActivationCode;
import com.airlock.iam.factor.domain.model.airlock2fa.activation.Airlock2FAActivationCodeShort;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAAdminHardwareDevice;
import com.airlock.iam.factor.domain.model.airlock2fa.order.Airlock2FAActivationLetterOrder;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
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.converter.BidirectionalConverter;
import ma.glasnost.orika.metadata.Type;

public class AdminAirlock2FAMapper {
   public AdminAirlock2FAMapper(MapperFactory factory) {
      this.configure(factory);
   }

   private void configure(MapperFactory factory) {
      String accountAllowedFactorsConverter = Airlock2FAUserAccountData.AllowedFactorData.class.getName() + "Converter";
      factory.getConverterFactory()
         .registerConverter(
            accountAllowedFactorsConverter,
            new AdminAirlock2FAMapper.Airlock2FAFactorSetConverter<Airlock2FAUserAccountData.AllowedFactorData>(
               Airlock2FAUserAccountData.AllowedFactorData::valueOf
            )
         );
      factory.classMap(Airlock2FAUserAccount.class, Airlock2FAUserAccountData.class)
         .field("userAttributes.displayName", "displayName")
         .field("userAttributes.createdAt", "createdAt")
         .field("userAttributes.updatedAt", "updatedAt")
         .field("userAttributes.failedAttempts", "failedAttempts")
         .field("userAttributes.maxFailedAttempts", "maxFailedAttempts")
         .fieldMap("userAttributes.allowedFactors", "allowedFactors")
         .converter(accountAllowedFactorsConverter)
         .add()
         .field("userAttributes.status", "status")
         .byDefault(new DefaultFieldMapper[0])
         .register();
      factory.classMap(Airlock2FAActivationLetterViewer.class, Airlock2FAActivationLetterData.class)
         .customize(new AdminAirlock2FAMapper.Airlock2FAActivationLetterMapper())
         .register();
      String appDeviceFactorsConverter = Airlock2FAAppDeviceFactorData.class.getName() + "Converter";
      factory.getConverterFactory()
         .registerConverter(appDeviceFactorsConverter, new AdminAirlock2FAMapper.Airlock2FAFactorSetConverter(Airlock2FAAppDeviceFactorData::valueOf));
      factory.classMap(Airlock2FAAdminAppDevice.class, Airlock2FAAppDeviceData.class)
         .field("type", "deviceType")
         .fieldMap("supportedFactors", "supportedFactors")
         .converter(appDeviceFactorsConverter)
         .add()
         .byDefault(new DefaultFieldMapper[0])
         .register();
      factory.getConverterFactory().registerConverter(new AdminAirlock2FAMapper.Airlock2FAHardwareTokenActivationCodeConverter());
      String hardwareDeviceFactorsConverter = UserAirlock2FAHardwareDeviceData.HardwareDeviceFactorData.class.getName() + "Converter";
      factory.getConverterFactory()
         .registerConverter(
            hardwareDeviceFactorsConverter,
            new AdminAirlock2FAMapper.Airlock2FAFactorSetConverter<UserAirlock2FAHardwareDeviceData.HardwareDeviceFactorData>(
               UserAirlock2FAHardwareDeviceData.HardwareDeviceFactorData::valueOf
            )
         );
      factory.classMap(Airlock2FAAdminHardwareDevice.class, UserAirlock2FAHardwareDeviceData.class)
         .field("displayName.value", "displayName")
         .fieldMap("supportedFactors", "supportedFactors")
         .converter(hardwareDeviceFactorsConverter)
         .add()
         .byDefault(new DefaultFieldMapper[0])
         .register();
      factory.classMap(Airlock2FAActivationLetterOrder.class, Airlock2FAActivationLetterOrderData.class)
         .byDefault(new DefaultFieldMapper[0])
         .fieldMap("creator", "orderedBy")
         .add()
         .register();
   }

   private static class Airlock2FAActivationLetterMapper extends CustomMapper<Airlock2FAActivationLetterViewer, Airlock2FAActivationLetterData> {
      public void mapAtoB(Airlock2FAActivationLetterViewer viewer, Airlock2FAActivationLetterData data, MappingContext context) {
         data.setCreatedAt(Date.from(viewer.created()));
         data.setExpiresAt(Date.from(viewer.expires()));
         data.setShortenedActivationCode(viewer.shortenedActivationCode());
         viewer.retrieveConfidentialActivation()
            .ifPresent(
               c -> data.setConfidential(
                  new Airlock2FAConfidentialActivationData(
                     c.getActivationCode().getValue(),
                     c.getActivationQrCode().getEncodedImage(),
                     c.getActivationCodeShort().<String>map(Airlock2FAActivationCodeShort::getValue).orElse(null)
                  )
               )
            );
      }
   }

   private static class Airlock2FAFactorSetConverter<T> extends CustomConverter<Airlock2FAFactorSet, List<T>> {
      private final Function<String, T> factory;

      Airlock2FAFactorSetConverter(Function<String, T> factory) {
         this.factory = factory;
      }

      public List<T> convert(Airlock2FAFactorSet source, Type<? extends List<T>> destinationType, MappingContext mappingContext) {
         return source.stream()
            .map(factor -> factor == Airlock2FAFactor.OFFLINE_QR_CODE ? "QR_CODE" : factor.name())
            .map(this.factory)
            .collect(Collectors.toList());
      }
   }

   private static class Airlock2FAHardwareTokenActivationCodeConverter extends BidirectionalConverter<Optional<Airlock2FAHardwareTokenActivationCode>, String> {
      public String convertTo(Optional<Airlock2FAHardwareTokenActivationCode> source, Type<String> destinationType, MappingContext mappingContext) {
         return source.<String>map(Airlock2FAHardwareTokenActivationCode::getCode).orElse(null);
      }

      public Optional<Airlock2FAHardwareTokenActivationCode> convertFrom(
         String source, Type<Optional<Airlock2FAHardwareTokenActivationCode>> destinationType, MappingContext mappingContext
      ) {
         return Optional.ofNullable(source).map(Airlock2FAHardwareTokenActivationCode::airlock2FAHardwareTokenActivationCode);
      }
   }
}
