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.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.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.UnifiedRuntimeDriverTimelineService;
import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
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);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
                .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);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
                .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 returnsBadRequestForInvalidRuntimeRequest() throws Exception {
        UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
        UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
                .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);
    }
}
