package com.airlock.iam.admin.application.configuration.generic;

import com.airlock.iam.admin.api.domain.model.token.generic.GenericToken;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.date.Dates;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class ContextDataMappingEntry extends MappingEntry {
   private static final ELogger LOG = ELoggerFactory.getLogger(ContextDataMappingEntry.class);
   private final Map<String, MappedContextDataField> internalToExternal;
   private final Map<String, ContextDataMappingEntry.InternalContextData> externalToInternal;

   public ContextDataMappingEntry(Map<String, MappedContextDataField> contextDataFields) {
      super(false);
      this.internalToExternal = contextDataFields;
      this.externalToInternal = contextDataFields.entrySet()
         .stream()
         .collect(
            Collectors.toMap(
               entry -> entry.getValue().getName(),
               entry -> new ContextDataMappingEntry.InternalContextData(entry.getKey(), entry.getValue().getDataType(), entry.getValue().getAccessType())
            )
         );
   }

   @Override
   void setAttributeValueOnToken(Object value, GenericToken token) {
      Map<String, Object> externalContextData = (Map<String, Object>)value;
      Map<String, Object> transformedExternalContextData = externalContextData.entrySet()
         .stream()
         .filter(externalContextDataEntry -> this.externalToInternal.containsKey(externalContextDataEntry.getKey()))
         .collect(HashMap::new, (map, externalContextDataEntry) -> {
            ContextDataMappingEntry.InternalContextData internal = this.externalToInternal.get(externalContextDataEntry.getKey());
            map.put(internal.getInternalName(), this.transformValue(externalContextDataEntry.getValue(), internal.getDataType()));
         }, HashMap::putAll);
      if (token.getAttribute("contextData") == null) {
         token.putAttribute("contextData", transformedExternalContextData);
      } else {
         token.<Map<String, Object>>getAttribute("contextData").putAll(transformedExternalContextData);
      }
   }

   private Object transformValue(Object value, CredentialBasedAttributeMapping.ContextDataType dataType) {
      try {
         return switch (dataType) {
            case STRING, INTEGER, BOOLEAN -> value;
            case DATE_TIME -> Dates.toDate((String)value);
         };
      } catch (ClassCastException e) {
         LOG.debug(String.format("Cannot convert %s to type %s", value, dataType));
         throw new ViolationException(String.format("Cannot convert value to type %s", dataType));
      }
   }

   Map<String, Object> getAttributeValueFromToken(GenericToken token) {
      Map<String, Object> contextData = token.getAttribute("contextData");
      if (contextData == null) {
         return null;
      }

      Map<String, Object> res = contextData.entrySet()
         .stream()
         .filter(entry -> this.internalToExternal.containsKey(entry.getKey()) && entry.getValue() != null)
         .collect(
            HashMap::new,
            (map, entry) -> map.put(this.internalToExternal.get(entry.getKey()).getName(), this.toExternalValue((Entry<String, Object>)entry)),
            HashMap::putAll
         );
      return res.isEmpty() ? null : res;
   }

   @Nullable
   private Object toExternalValue(Entry<String, Object> internal) {
      MappedContextDataField mapping = this.internalToExternal.get(internal.getKey());
      return mapping.getAccessType() == CredentialBasedAttributeMapping.ContextDataAttributeAccessType.WRITE_ONLY ? null : internal.getValue();
   }

   @Override
   ValidationResult validate(String key, Object object) {
      ValidationResult result = ValidationResult.valid();
      Map<String, Object> externalContextData = (Map<String, Object>)object;
      externalContextData.forEach((contextDataKey, value) -> {
         ContextDataMappingEntry.InternalContextData internal = this.externalToInternal.get(contextDataKey);
         if (internal != null && internal.getAccessRestriction() == CredentialBasedAttributeMapping.ContextDataAttributeAccessType.READ_ONLY) {
            result.mergeWith(ValidationResult.singleError("contextData." + contextDataKey, GenericError.READ_ONLY));
         }
      });
      return result;
   }

   private static final class InternalContextData {
      private final String internalName;
      private final CredentialBasedAttributeMapping.ContextDataType dataType;
      private final CredentialBasedAttributeMapping.ContextDataAttributeAccessType accessRestriction;

      @SuppressFBWarnings(justification = "generated code")
      public InternalContextData(
         String internalName,
         CredentialBasedAttributeMapping.ContextDataType dataType,
         CredentialBasedAttributeMapping.ContextDataAttributeAccessType accessRestriction
      ) {
         this.internalName = internalName;
         this.dataType = dataType;
         this.accessRestriction = accessRestriction;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getInternalName() {
         return this.internalName;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CredentialBasedAttributeMapping.ContextDataType getDataType() {
         return this.dataType;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CredentialBasedAttributeMapping.ContextDataAttributeAccessType getAccessRestriction() {
         return this.accessRestriction;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof ContextDataMappingEntry.InternalContextData other)) {
            return false;
         } else {
            Object this$internalName = this.internalName;
            Object other$internalName = other.internalName;
            if (this$internalName == null ? other$internalName == null : this$internalName.equals(other$internalName)) {
               Object this$dataType = this.dataType;
               Object other$dataType = other.dataType;
               if (this$dataType == null ? other$dataType == null : this$dataType.equals(other$dataType)) {
                  Object this$accessRestriction = this.accessRestriction;
                  Object other$accessRestriction = other.accessRestriction;
                  return this$accessRestriction == null ? other$accessRestriction == null : this$accessRestriction.equals(other$accessRestriction);
               } else {
                  return false;
               }
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $internalName = this.internalName;
         result = result * 59 + ($internalName == null ? 43 : $internalName.hashCode());
         Object $dataType = this.dataType;
         result = result * 59 + ($dataType == null ? 43 : $dataType.hashCode());
         Object $accessRestriction = this.accessRestriction;
         return result * 59 + ($accessRestriction == null ? 43 : $accessRestriction.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "ContextDataMappingEntry.InternalContextData(internalName="
            + this.getInternalName()
            + ", dataType="
            + this.getDataType()
            + ", accessRestriction="
            + this.getAccessRestriction()
            + ")";
      }
   }
}
