package com.airlock.iam.common.api.domain.model.event;

import com.airlock.iam.base.api.domain.model.event.EventId;
import com.airlock.iam.base.api.domain.model.event.EventType;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

public record EventData(EventId eventId, EventType eventType, EventData.EventPayload payload, Instant createdAt) {
   private static final Pattern TYPE_PATTERN = Pattern.compile("[A-Z0-9_]+");

   public EventData {
      validateType(eventType);
   }

   private static void validateType(EventType type) {
      if (!TYPE_PATTERN.matcher(type.name()).matches()) {
         throw new ViolationException("Invalid event type: " + type.name());
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static EventData.EventDataBuilder builder() {
      return new EventData.EventDataBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class EventDataBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private EventId eventId;
      @SuppressFBWarnings(justification = "generated code")
      private EventType eventType;
      @SuppressFBWarnings(justification = "generated code")
      private EventData.EventPayload payload;
      @SuppressFBWarnings(justification = "generated code")
      private Instant createdAt;

      @SuppressFBWarnings(justification = "generated code")
      EventDataBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public EventData.EventDataBuilder eventId(EventId eventId) {
         this.eventId = eventId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EventData.EventDataBuilder eventType(EventType eventType) {
         this.eventType = eventType;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EventData.EventDataBuilder payload(EventData.EventPayload payload) {
         this.payload = payload;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EventData.EventDataBuilder createdAt(Instant createdAt) {
         this.createdAt = createdAt;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EventData build() {
         return new EventData(this.eventId, this.eventType, this.payload, this.createdAt);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "EventData.EventDataBuilder(eventId="
            + this.eventId
            + ", eventType="
            + this.eventType
            + ", payload="
            + this.payload
            + ", createdAt="
            + this.createdAt
            + ")";
      }
   }

   public record EventPayload(Map<String, Object> value) {
      public EventPayload {
         validateMap(value);
      }

      private static void validateMap(Map<?, ?> value) {
         value.values().forEach(EventData.EventPayload::validateValue);
      }

      private static void validateValue(Object v) {
         if (v instanceof Map<?, ?> mapValue) {
            validateMap(mapValue);
         } else if (v instanceof List<?> collectionValue) {
            collectionValue.forEach(EventData.EventPayload::validateValue);
         } else if (v != null && !isValidSimpleType(v)) {
            throw new ViolationException("Invalid payload data type: " + v.getClass());
         }
      }

      private static boolean isValidSimpleType(Object v) {
         return v instanceof String || v instanceof Number || v instanceof Boolean || v instanceof Instant || v instanceof LocalDate || v instanceof Date;
      }
   }
}
