package com.airlock.iam.login.infrastructure.session;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.impl.domain.model.step.AuthenticationStepResultImpl;
import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.domain.model.mapification.MapificationUtils;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.authentication.factor.AuthenticationFactor;
import com.airlock.iam.common.api.domain.model.authentication.factor.AuthenticationFactorDetail;
import com.airlock.iam.common.api.domain.model.authentication.factor.SimpleAuthenticationFactorDetail;
import com.airlock.iam.common.domain.service.mapification.Mapifier;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactor;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.flow.api.domain.model.step.AdditionalAttributes;
import com.airlock.iam.flow.api.domain.model.step.InternalStepResultData;
import com.airlock.iam.flow.api.domain.model.step.SimpleStepAction;
import com.airlock.iam.flow.api.domain.model.step.SimpleStepError;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepError;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultData;
import com.airlock.iam.flow.impl.domain.model.step.StepResultImpl;
import com.airlock.iam.flow.shared.domain.model.mapification.AuthenticationDeviceUsageMapifier;
import com.airlock.iam.flow.shared.domain.model.session.StepResultMapifier;
import com.airlock.iam.flow.shared.domain.service.tag.TagMapifier;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Arrays;
import java.util.Map;

public final class StepResultMapifierImpl implements StepResultMapifier {
   private static final String CLASS_KEY = "class";
   private static final String TYPE_KEY = "type";
   private static final String STEP_RESULT_KEY = "stepResult";
   private static final String NEXT_ACTION_KEY = "nextAction";
   private static final String ERROR_KEY = "error";
   private static final String OBTAINED_TAGS_KEY = "obtainedTags";
   private static final String RESULT_DATA_KEY = "resultData";
   private static final String INTERNAL_RESULT_DATA_KEY = "internalResultData";
   private static final String METADATA_KEY = "metaData";
   private static final String ADDITIONAL_ATTRIBUTES_KEY = "additionalAttributes";
   private static final String AUTH_FACTOR_KEY = "authFactor";
   private static final String AUTH_FACTOR_DETAIL_KEY = "details";
   private static final String AUTH_FACTOR_DETAIL_ID_KEY = "detailsId";
   private static final String IS_FAILED_FACTOR_KEY = "failedFactor";
   private static final String AUTH_DEVICE_USAGE_KEY = "authDeviceUsage";
   private final Provider<TagMapifier> tagMapifier;
   private final Provider<Mapifier> mapifier;

   @Inject
   public StepResultMapifierImpl(Provider<TagMapifier> tagMapifier, Provider<Mapifier> mapifier) {
      this.tagMapifier = tagMapifier;
      this.mapifier = mapifier;
   }

   @Override
   public MapifiedValues mapify(StepResult stepResult) {
      if (stepResult instanceof AuthenticationStepResultImpl authStepResult) {
         return this.mapifyAuthStepResult(authStepResult);
      } else if (stepResult instanceof StepResultImpl stepResultImpl) {
         return this.mapifyStepResultImpl(stepResultImpl);
      } else {
         throw new RuntimeException("Unknown " + LogUtil.quote(StepResult.class.getName()) + " class: " + LogUtil.quote(stepResult.getClass().getName()));
      }
   }

   @Override
   public StepResult demapify(MapifiedValues values) {
      String className = values.get("class");
      if (serializeClass(AuthenticationStepResultImpl.class).equals(className)) {
         return this.demapifyAuthStepResult(values);
      } else if (serializeClass(StepResultImpl.class).equals(className)) {
         return this.demapifyStepResultImpl(values);
      } else {
         throw new RuntimeException("Unknown " + LogUtil.quote(StepResult.class.getName()) + " class: " + LogUtil.quote(className));
      }
   }

   private MapifiedValues mapifyStepResultImpl(StepResultImpl stepResult) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("class", serializeClass(StepResultImpl.class))
         .put("type", stepResult.type().name())
         .putIfPresent("nextAction", stepResult.nextAction().map(StepAction::name))
         .putIfPresent("error", stepResult.error().map(StepError::name))
         .putIfNotEmpty("obtainedTags", MapificationUtils.mapifyCollection(stepResult.obtainedTags(), ((TagMapifier)this.tagMapifier.get())::mapifyTag))
         .putIfPresent("resultData", stepResult.<StepResultData>resultData().map(((Mapifier)this.mapifier.get())::mapify))
         .putIfPresent("internalResultData", stepResult.<InternalStepResultData>internalResultData().map(((Mapifier)this.mapifier.get())::mapify))
         .put("metaData", MapificationUtils.mapifyMap(stepResult.metadata(), ((Mapifier)this.mapifier.get())::mapify))
         .put("additionalAttributes", MapificationUtils.mapifyMap(stepResult.additionalAttributes().asMap(), ((Mapifier)this.mapifier.get())::mapify))
         .build();
   }

   private StepResultImpl demapifyStepResultImpl(MapifiedValues values) {
      return new StepResultImpl(
         values.simpleValue("type", o -> StepResult.StepResultType.valueOf((String)o)),
         values.<String>getOptional("nextAction").map(SimpleStepAction::new),
         values.<String>getOptional("error").map(SimpleStepError::new),
         values.demapifySet("obtainedTags", ((TagMapifier)this.tagMapifier.get())::demapifyTag),
         values.demapifyOptional("resultData", ((Mapifier)this.mapifier.get())::demapify),
         values.demapifyOptional("internalResultData", ((Mapifier)this.mapifier.get())::demapify),
         values.simpleValue("metaData", v -> MapificationUtils.demapifyMap(v, ((Mapifier)this.mapifier.get())::demapify)),
         values.simpleValue("additionalAttributes", this::demapifyAdditionalAttributes)
      );
   }

   private MapifiedValues mapifyAuthStepResult(AuthenticationStepResultImpl stepResult) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("class", serializeClass(AuthenticationStepResultImpl.class))
         .put("stepResult", this.mapifyStepResultImpl((StepResultImpl)stepResult.getStepResult()))
         .put("authFactor", stepResult.getAuthenticationFactor().getIdentifier())
         .putIfPresent("details", stepResult.getAuthenticationFactorDetail().map(StepResultMapifierImpl::mapifyAuthFactorDetail))
         .put("failedFactor", stepResult.isFailedFactor())
         .putIfPresent("authDeviceUsage", stepResult.getDeviceUsageData().map(AuthenticationDeviceUsageMapifier::mapifyAuthDeviceUsage))
         .build();
   }

   private AuthenticationStepResult demapifyAuthStepResult(MapifiedValues values) {
      return new AuthenticationStepResultImpl(
         values.simpleValue("stepResult", v -> this.demapifyStepResultImpl(new MapifiedValues((Map<String, Object>)v))),
         getAuthFactor(values),
         values.demapifyOptional("details", StepResultMapifierImpl::demapifyAuthFactorDetail),
         values.<Boolean>get("failedFactor"),
         values.demapifyOptional("authDeviceUsage", AuthenticationDeviceUsageMapifier::demapifyAuthDeviceUsage)
      );
   }

   private AdditionalAttributes demapifyAdditionalAttributes(Object o) {
      return new AdditionalAttributes(MapificationUtils.demapifyMap(o, ((Mapifier)this.mapifier.get())::demapify));
   }

   private static AuthenticationFactor demapifyAuthFactor(String authFactorId) {
      return Arrays.stream(KnownAuthenticationFactor.values())
         .filter(v -> v.getIdentifier().equals(authFactorId))
         .findFirst()
         .map(AuthenticationFactor.class::cast)
         .orElse(() -> authFactorId);
   }

   private static MapifiedValues mapifyAuthFactorDetail(AuthenticationFactorDetail detail) {
      return MapifiedValuesBuilder.mapifiedValues().put("detailsId", detail.getIdentifier()).put("authFactor", detail.getFactor().getIdentifier()).build();
   }

   private static AuthenticationFactorDetail demapifyAuthFactorDetail(MapifiedValues values) {
      String identifier = values.get("detailsId");
      AuthenticationFactor authFactor = getAuthFactor(values);
      return Arrays.stream(KnownAuthenticationFactorDetail.values())
         .filter(v -> v.getIdentifier().equals(identifier))
         .findFirst()
         .map(AuthenticationFactorDetail.class::cast)
         .orElse(new SimpleAuthenticationFactorDetail(identifier, authFactor));
   }

   private static AuthenticationFactor getAuthFactor(MapifiedValues values) {
      return values.simpleValue("authFactor", StepResultMapifierImpl::demapifyAuthFactor);
   }

   private static String serializeClass(Class<?> clazz) {
      return clazz.getName();
   }
}
