package at.procon.eventhub.processing.api;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import at.procon.eventhub.dto.EventDetailsDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
import at.procon.eventhub.processing.eventprocessing.RuntimeEventProcessingService;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.service.UnifiedRuntimeDerivedProjectionService;
import at.procon.eventhub.processing.service.UnifiedRuntimeDriverTimelineService;
import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

class UnifiedRuntimeProcessingControllerTest {

    private final ObjectMapper objectMapper = testObjectMapper();

    @Test
    void loadsDriverEventsViaRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService, derivedProjectionService))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        UUID sessionId = UUID.randomUUID();
        UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forTachographFileSession(
                sessionId,
                "12:123",
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                true,
                0
        );
        when(eventAssemblyService.assembleDriverScopedEvents(any()))
                .thenReturn(new UnifiedRuntimeEventBundle(
                        request,
                        List.of(event("EV-1", "2026-05-01T08:00:00Z")),
                        List.of(new UnifiedDiscoveredVehicleRef("VEH-1", "VIN-1", "12", "REG-1")),
                        List.of(),
                        List.of(event("EV-1", "2026-05-01T08:00:00Z")),
                        List.of("note")
                ));

        mockMvc.perform(post("/api/eventhub/runtime-processing/driver-events")
                        .contentType("application/json")
                        .content("""
                                {
                                  "sessionId": "%s",
                                  "sourceFamilies": ["TACHOGRAPH_FILE_SESSION"],
                                  "driverKey": "12:123",
                                  "occurredFrom": "2026-05-01T08:00:00Z",
                                  "occurredTo": "2026-05-01T10:00:00Z",
                                  "expandVehicleEvents": true,
                                  "vehicleExpansionPaddingMinutes": 0
                                }
                                """.formatted(sessionId)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.request.sessionId").value(sessionId.toString()))
                .andExpect(jsonPath("$.request.driverKey").value("12:123"))
                .andExpect(jsonPath("$.discoveredVehicles[0].vin").value("VIN-1"))
                .andExpect(jsonPath("$.mergedEvents[0].externalSourceEventId").value("EV-1"))
                .andExpect(jsonPath("$.mergedEvents[0].payload.raw.driverKey").value("12:123"))
                .andExpect(jsonPath("$.mergedEvents[0].eventDetails.attributes.cardSlot").value("DRIVER"));
    }

    @Test
    void loadsDriverTimelineViaRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService, derivedProjectionService))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        when(timelineService.loadDriverTimeline(any()))
                .thenReturn(new ResolvedDriverTimeline(
                        "UNIFIED_EVENT_STREAM",
                        OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                        OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                        List.of(ResolvedVehicleUsageInterval.resolved(
                                null,
                                "DRIVER:42",
                                "CVU-1",
                                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "DRIVER_CARD",
                                List.of("CVU-1")
                        )),
                        List.of(new ResolvedActivityInterval(
                                "ACT-1",
                                OffsetDateTime.parse("2026-05-01T08:30:00Z"),
                                OffsetDateTime.parse("2026-05-01T09:00:00Z"),
                                1800L,
                                "DRIVE",
                                "DRIVER",
                                "INSERTED",
                                "SINGLE",
                                "12:REG-1",
                                "VIN-1",
                                "DRIVER_CARD",
                                List.of("ACT-1"),
                                false,
                                false,
                                "RAW_INTERVAL"
                        )),
                        List.of(),
                        List.of()
                ));

        mockMvc.perform(post("/api/eventhub/runtime-processing/driver-timeline")
                        .contentType("application/json")
                        .content("""
                                {
                                  "tenantKey": "default",
                                  "sourceFamilies": ["TACHOGRAPH_DB", "YELLOWFOX_DB"],
                                  "eventBackend": "SOURCE_DB",
                                  "driverSourceEntityId": "DRIVER:42",
                                  "occurredFrom": "2026-05-01T08:00:00Z",
                                  "occurredTo": "2026-05-01T10:00:00Z",
                                  "expandVehicleEvents": false,
                                  "vehicleExpansionPaddingMinutes": 15
                                }
                                """))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.sourceKind").value("UNIFIED_EVENT_STREAM"))
                .andExpect(jsonPath("$.activityIntervals[0].intervalId").value("ACT-1"))
                .andExpect(jsonPath("$.vehicleUsageIntervals[0].intervalId").value("CVU-1"));
    }


    @Test
    void loadsDriverDerivedProjectionsViaRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService, derivedProjectionService))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        UUID sessionId = UUID.randomUUID();
        UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forTachographFileSession(
                sessionId,
                "12:123",
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                true,
                0
        );
        TachographEsperDriverProcessingResultDto projection = new TachographEsperDriverProcessingResultDto(
                sessionId,
                "12:123",
                "UNIFIED_EVENT_STREAM",
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                0,
                0,
                1,
                0,
                1,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of("runtime derived")
        );
        when(derivedProjectionService.loadDriverDerivedProjections(any()))
                .thenReturn(new UnifiedRuntimeDerivedProjectionResultDto(
                        request,
                        2,
                        1,
                        1,
                        3,
                        List.of(new UnifiedDiscoveredVehicleRef("VEH-1", "VIN-1", "12", "REG-1")),
                        projection,
                        List.of("runtime derived")
                ));

        mockMvc.perform(post("/api/eventhub/runtime-processing/driver-derived-projections")
                        .contentType("application/json")
                        .content("""
                                {
                                  "sessionId": "%s",
                                  "sourceFamilies": ["TACHOGRAPH_FILE_SESSION"],
                                  "driverKey": "12:123",
                                  "occurredFrom": "2026-05-01T08:00:00Z",
                                  "occurredTo": "2026-05-01T10:00:00Z",
                                  "expandVehicleEvents": true,
                                  "significantDrivingMinutes": 3,
                                  "minimumRestPeriodMinutes": 720
                                }
                                """.formatted(sessionId)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.mergedEventCount").value(3))
                .andExpect(jsonPath("$.projection.driverKey").value("12:123"))
                .andExpect(jsonPath("$.projection.drivingInterruptionIntervalCount").value(1))
                .andExpect(jsonPath("$.projection.dailyWeeklyRestCandidateIntervalCount").value(1));
    }


    @Test
    void listsRuntimeEventProcessingProfilesViaRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        RuntimeEventProcessingService runtimeEventProcessingService = org.mockito.Mockito.mock(RuntimeEventProcessingService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(
                        eventAssemblyService,
                        timelineService,
                        derivedProjectionService,
                        null,
                        runtimeEventProcessingService
                ))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        when(runtimeEventProcessingService.listProfiles())
                .thenReturn(List.of(new RuntimeEventProcessingProfileDescriptorDto(
                        "tachograph-driver-esper-v1",
                        "Tachograph Driver Esper Processing",
                        "Runs tachograph driver Esper processing over runtime event scopes.",
                        RuntimeEventPartitioningStrategy.DRIVER,
                        List.of(RuntimeEventPartitioningStrategy.DRIVER),
                        Set.of(),
                        Set.of("significantDrivingMinutes", "minimumRestPeriodMinutes")
                )));

        mockMvc.perform(get("/api/eventhub/runtime-processing/event-processing/profiles"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].profileKey").value("tachograph-driver-esper-v1"))
                .andExpect(jsonPath("$[0].displayName").value("Tachograph Driver Esper Processing"))
                .andExpect(jsonPath("$[0].defaultPartitioningStrategy").value("DRIVER"))
                .andExpect(jsonPath("$[0].supportedPartitioningStrategies[0]").value("DRIVER"))
                .andExpect(jsonPath("$[0].optionalParameters[0]").exists());
    }

    @Test
    void runsGenericEventProcessingProfileViaRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        RuntimeEventProcessingService runtimeEventProcessingService = org.mockito.Mockito.mock(RuntimeEventProcessingService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(
                        eventAssemblyService,
                        timelineService,
                        derivedProjectionService,
                        null,
                        runtimeEventProcessingService
                ))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        UUID sessionId = UUID.randomUUID();
        UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forTachographFileSession(
                sessionId,
                "12:123",
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                true,
                0
        );
        when(runtimeEventProcessingService.process(any()))
                .thenReturn(new RuntimeEventProcessingResultDto(
                        "tachograph-driver-esper-v1",
                        RuntimeEventPartitioningStrategy.DRIVER,
                        request,
                        3,
                        1,
                        1,
                        List.of(new UnifiedDiscoveredVehicleRef("VEH-1", "VIN-1", "12", "REG-1")),
                        Map.of(),
                        List.of("generic profile"),
                        List.of()
                ));

        mockMvc.perform(post("/api/eventhub/runtime-processing/event-processing")
                        .contentType("application/json")
                        .content("""
                                {
                                  "profileKey": "tachograph-driver-esper-v1",
                                  "scope": {
                                    "sessionId": "%s",
                                    "sourceFamilies": ["TACHOGRAPH_FILE_SESSION"],
                                    "driverKey": "12:123",
                                    "occurredFrom": "2026-05-01T08:00:00Z",
                                    "occurredTo": "2026-05-01T10:00:00Z"
                                  },
                                  "partitioning": {
                                    "strategy": "DRIVER",
                                    "includeAllPartitions": false
                                  },
                                  "parameters": {
                                    "significantDrivingMinutes": 3,
                                    "minimumRestPeriodMinutes": 720
                                  }
                                }
                                """.formatted(sessionId)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.profileKey").value("tachograph-driver-esper-v1"))
                .andExpect(jsonPath("$.partitioningStrategy").value("DRIVER"))
                .andExpect(jsonPath("$.inputEventCount").value(3))
                .andExpect(jsonPath("$.discoveredVehicles[0].vin").value("VIN-1"));
    }


    @Test
    void compatibilityTachographEndpointDelegatesThroughGenericProfileRuntimeApi() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        RuntimeEventProcessingService runtimeEventProcessingService = org.mockito.Mockito.mock(RuntimeEventProcessingService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(
                        eventAssemblyService,
                        timelineService,
                        derivedProjectionService,
                        null,
                        runtimeEventProcessingService
                ))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        UUID sessionId = UUID.randomUUID();
        UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forTachographFileSession(
                sessionId,
                "12:123",
                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                true,
                0
        );
        UnifiedRuntimeDerivedProjectionResultDto driverResult = new UnifiedRuntimeDerivedProjectionResultDto(
                request,
                2,
                1,
                3,
                5,
                List.of(new UnifiedDiscoveredVehicleRef("VEH-1", "VIN-1", "12", "REG-1")),
                null,
                List.of("processed through generic profile")
        );
        when(runtimeEventProcessingService.process(any()))
                .thenReturn(new RuntimeEventProcessingResultDto(
                        "tachograph-driver-esper-v1",
                        RuntimeEventPartitioningStrategy.DRIVER,
                        request,
                        5,
                        1,
                        1,
                        List.of(new UnifiedDiscoveredVehicleRef("VEH-1", "VIN-1", "12", "REG-1")),
                        Map.of("12:123", new RuntimeEventProcessingPartitionResultDto(
                                "DRIVER",
                                "12:123",
                                "UnifiedRuntimeDerivedProjectionResultDto",
                                driverResult,
                                Map.of("mergedEventCount", 5)
                        )),
                        List.of("generic adapter"),
                        List.of()
                ));

        mockMvc.perform(post("/api/eventhub/runtime-processing/tachograph/esper-processing")
                        .contentType("application/json")
                        .content("""
                                {
                                  "sessionId": "%s",
                                  "sourceFamilies": ["TACHOGRAPH_FILE_SESSION"],
                                  "driverKey": "12:123",
                                  "occurredFrom": "2026-05-01T08:00:00Z",
                                  "occurredTo": "2026-05-01T10:00:00Z",
                                  "expandVehicleEvents": true,
                                  "significantDrivingMinutes": 3,
                                  "minimumRestPeriodMinutes": 720
                                }
                                """.formatted(sessionId)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.inputEventCount").value(5))
                .andExpect(jsonPath("$.selectedDriverCount").value(1))
                .andExpect(jsonPath("$.driverResults['12:123'].mergedEventCount").value(5))
                .andExpect(jsonPath("$.notes[0]").value("generic adapter"));
    }

    @Test
    void returnsBadRequestForInvalidRuntimeRequest() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService, derivedProjectionService))
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
                .build();

        mockMvc.perform(post("/api/eventhub/runtime-processing/driver-events")
                        .contentType("application/json")
                        .content("""
                                {
                                  "sourceFamilies": ["TACHOGRAPH_DB"],
                                  "occurredFrom": "2026-05-01T08:00:00Z",
                                  "occurredTo": "2026-05-01T10:00:00Z"
                                }
                                """))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.message").exists());
    }

    private EventHubEventDto event(String externalId, String occurredAt) {
        return new EventHubEventDto(
                UUID.randomUUID(),
                externalId,
                new DriverRefDto("12:123", null),
                new VehicleRefDto("VEH-1", "VIN-1", "VR-1", new VehicleRegistrationRefDto("12", 12, "REG-1")),
                OffsetDateTime.parse(occurredAt),
                null,
                OffsetDateTime.parse(occurredAt),
                EventDomain.DRIVER_ACTIVITY,
                EventType.DRIVE,
                EventLifecycle.START,
                null,
                null,
                new EventDetailsDto("DRIVER_ACTIVITY", objectMapper.valueToTree(Map.of("cardSlot", "DRIVER"))),
                null,
                objectMapper.valueToTree(Map.of("raw", Map.of("driverKey", "12:123", "intervalId", "ACT-1"))),
                false,
                null
        );
    }

    private ObjectMapper testObjectMapper() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(OffsetDateTime.class, new StdScalarSerializer<>(OffsetDateTime.class) {
            @Override
            public void serialize(OffsetDateTime value, JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider provider)
                    throws java.io.IOException {
                gen.writeString(value == null ? null : value.toString());
            }
        });
        module.addDeserializer(OffsetDateTime.class, new StdScalarDeserializer<>(OffsetDateTime.class) {
            @Override
            public OffsetDateTime deserialize(JsonParser p, com.fasterxml.jackson.databind.DeserializationContext ctxt)
                    throws java.io.IOException {
                String value = p.getValueAsString();
                return value == null || value.isBlank() ? null : OffsetDateTime.parse(value);
            }
        });
        return new ObjectMapper().registerModule(module);
    }
}
