package com.airlock.iam.base.domain.model.mapification;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.misc.data.OptionalString;
import com.airlock.iam.base.misc.util.Base64Util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.function.Function;

public final class MapificationUtils {
   public static final String ENUM_NAME_KEY = "enumName";
   private static final String SECOND_KEY = "second";
   private static final String NANO_KEY = "nano";
   private static final String CLASSNAME_KEY = "class";
   private static final String HAS_VALUE_KEY = "hasValue";
   private static final String VALUE_KEY = "value";
   private static final String EPOCH_DAYS_KEY = "days";
   private static final String BYTES_KEY = "bytes";

   public static <T> List<Object> mapifyCollection(Collection<T> collection, Function<T, Object> mapifier) {
      return collection.stream().map(mapifier).toList();
   }

   public static MapifiedValues mapifyDuration(Duration duration) {
      return MapifiedValuesBuilder.mapifiedValues().put("second", duration.getSeconds()).put("nano", duration.getNano()).build();
   }

   public static Duration demapifyDuration(MapifiedValues values) {
      return Duration.ofSeconds(values.simpleValue("second", MapificationUtils::toLong), values.simpleValue("nano", MapificationUtils::toLong));
   }

   public static MapifiedValues mapifyInstant(Instant instant) {
      return MapifiedValuesBuilder.mapifiedValues().put("second", instant.getEpochSecond()).put("nano", instant.getNano()).build();
   }

   public static Instant demapifyInstant(MapifiedValues values) {
      return Instant.ofEpochSecond(values.simpleValue("second", MapificationUtils::toLong), values.simpleValue("nano", MapificationUtils::toLong));
   }

   public static MapifiedValues mapifyMap(Map<String, Object> map, Function<Object, MapifiedValues> mapifier) {
      MapifiedValuesBuilder result = MapifiedValuesBuilder.mapifiedValues();

      for (Entry<String, Object> entry : map.entrySet()) {
         result.put(entry.getKey(), mapifier.apply(entry.getValue()));
      }

      return result.build();
   }

   public static Map<String, Object> demapifyMap(Object object, Function<MapifiedValues, Object> demapifier) {
      Map<String, Object> result = new HashMap<>();
      Map<String, Map<String, Object>> mapified = (Map<String, Map<String, Object>>)object;
      mapified.forEach((key, value) -> result.put(key, demapifier.apply(new MapifiedValues((Map<String, Object>)value))));
      return result;
   }

   public static long toLong(Object rawNumber) {
      return ((Number)rawNumber).longValue();
   }

   public static <T> T demapifyEnum(MapifiedValues values) {
      return demapifyEnumOfClassname(values.get("enumName"), values.get("class"));
   }

   public static <T> T demapifyEnumOfClassname(String v, String className) {
      try {
         Class<T> clazz = (Class<T>)Class.forName(className);
         return demapifyEnumOfClass(v, clazz);
      } catch (ClassNotFoundException e) {
         throw new FailureException(e);
      }
   }

   public static <T> T demapifyEnumOfClass(String v, Class<T> clazz) {
      try {
         Method valueOf = clazz.getMethod("valueOf", String.class);
         return (T)valueOf.invoke(null, v);
      } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
         throw new FailureException(e);
      }
   }

   public static MapifiedValues mapifyEnum(Enum<?> object) {
      return MapifiedValuesBuilder.mapifiedValues().put("enumName", object.name()).put("class", object.getClass().getName()).build();
   }

   public static MapifiedValues mapifyOptionalString(OptionalString value) {
      return MapifiedValuesBuilder.mapifiedValues().put("hasValue", value.hasValue()).putIfPresent("value", Optional.ofNullable(value.value())).build();
   }

   public static OptionalString demapifyOptionalString(MapifiedValues values) {
      return new OptionalString(values.simpleValue("hasValue", Boolean.class::cast), values.<String>getOptional("value").orElse(null));
   }

   public static MapifiedValues mapifyDate(Date dateValue) {
      return mapifyInstant(dateValue.toInstant());
   }

   public static Date demapifyDate(MapifiedValues values) {
      return Date.from(demapifyInstant(values));
   }

   public static MapifiedValues mapifyLocalDate(LocalDate localDateValue) {
      return MapifiedValuesBuilder.mapifiedValues().put("days", localDateValue.toEpochDay()).build();
   }

   public static LocalDate demapifyLocalDate(MapifiedValues values) {
      return LocalDate.ofEpochDay(values.simpleValue("days", MapificationUtils::toLong));
   }

   public static MapifiedValues mapifyByteArray(byte[] bytes) {
      return MapifiedValuesBuilder.mapifiedValues().put("bytes", Base64Util.encodeToString(bytes)).build();
   }

   public static byte[] demapifyByteArray(MapifiedValues values) {
      return Base64Util.decode(values.get("bytes"));
   }

   public static MapifiedValues mapifyUnixTimestamp(Instant instant) {
      return MapifiedValuesBuilder.mapifiedValues().put("second", instant.getEpochSecond()).build();
   }

   public static Instant demapifyUnixTimestamp(MapifiedValues values) {
      return Instant.ofEpochSecond(values.simpleValue("second", MapificationUtils::toLong));
   }
}
