package com.airlock.iam.common.infrastructure.jackson;

import ch.ergon.restal.jsonapi.jackson.JsonApiJacksonConfigurator;
import com.airlock.iam.base.misc.date.Dates;
import com.airlock.iam.common.infrastructure.HK2Configurator;
import com.airlock.iam.common.infrastructure.factory.ObjectMapperFactory;
import com.airlock.iam.core.misc.util.locale.LocaleUtil;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.core.Configurable;
import jakarta.ws.rs.ext.ContextResolver;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;

public class JacksonConfigurator {
   public void configureJackson(Configurable<?> configurableApplication, HK2Configurator.HK2Binders binders) {
      setJsonFactoryConstraints();
      ObjectMapper objectMapper = createObjectMapper();
      configurableApplication.register(JacksonJsonProvider.class);
      configurableApplication.register(new JacksonConfigurator.MapperContextResolver(objectMapper));
      binders.addFactoryBinderFor(ObjectMapperFactory.class);
   }

   public static void setJsonFactoryConstraints() {
      StreamReadConstraints.overrideDefaultStreamReadConstraints(
         StreamReadConstraints.builder().maxNumberLength(1000).maxNestingDepth(1000).maxStringLength(100000000).build()
      );
   }

   public static ObjectMapper createObjectMapper() {
      ObjectMapper mapper = new ObjectMapper();
      mapper.setDefaultPropertyInclusion(Include.NON_NULL);
      mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
      mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      mapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true);
      mapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, true);
      mapper.enable(new Feature[]{Feature.STRICT_DUPLICATE_DETECTION});
      mapper.setDateFormat(new SimpleDateFormat(Dates.getDatePattern(), LocaleUtil.getForSystemStrings()));
      JsonApiJacksonConfigurator.configure(mapper, new String[0]);
      registerSerializers(mapper);
      return mapper;
   }

   private static void registerSerializers(ObjectMapper objectMapper) {
      SimpleModule module = new SimpleModule("IamModule", new Version(1, 0, 0, null, null, null));
      module.addSerializer(new OptionalStringSerializer());
      module.addSerializer(new OptionalBooleanSerializer());
      module.addSerializer(new LocalDateSerializer());
      module.addSerializer(new InstantSerializer());
      module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
      module.addDeserializer(Date.class, new IamDateDeserializer());
      objectMapper.registerModule(module);
   }

   private static class MapperContextResolver implements ContextResolver<ObjectMapper> {
      private final ObjectMapper mapper;

      public ObjectMapper getContext(Class<?> type) {
         return this.mapper;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MapperContextResolver(ObjectMapper mapper) {
         this.mapper = mapper;
      }
   }
}
