package at.procon.eventhub.esperpoc.service;

import static org.assertj.core.api.Assertions.assertThat;

import at.procon.eventhub.esperpoc.dto.DtiBoundaryPositionDto;
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVehicleDto;
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVicinityEventDto;
import at.procon.eventhub.esperpoc.dto.DrivingInterruptionDto;
import at.procon.eventhub.esperpoc.dto.EnrichedDtiIntervalDto;
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentRequest;
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodEngineMode;
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
import at.procon.eventhub.esperpoc.dto.EsperSupportEventDto;
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode;
import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto;
import at.procon.eventhub.esperpoc.dto.ShiftDrivingEvaluationDto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;

class EsperDtiEnrichmentServiceTest {

    private final EsperDtiEnrichmentService service = new EsperDtiEnrichmentService(null, null);

    @Test
    void mergesVehicleIntervalsAndPrefersIwCycleAsAuthoritativeSource() {
        UUID driverId = UUID.randomUUID();
        UUID vehicleId = UUID.randomUUID();
        UUID registrationId = UUID.randomUUID();

        List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> merged = service.mergeVehicleUsageIntervals(
                service.buildVehicleUsageIntervals(List.of(
                        supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
                        supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T12:00:00Z", null, null),
                        supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "IW_CYCLE", "iw1", "2026-04-01T08:05:00Z", null, null),
                        supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "IW_CYCLE", "iw1", "2026-04-01T11:55:00Z", null, null)
                ))
        );

        assertThat(merged).hasSize(1);
        assertThat(merged.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
        assertThat(merged.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T12:00:00Z"));
        assertThat(merged.get(0).authoritativeSource()).isEqualTo("IW_CYCLE");
        assertThat(merged.get(0).sourceRowIds()).containsExactly("cv1", "iw1");
    }

    @Test
    void mergesCardVehiclesUsedAcrossMidnightBoundaryWhenCardRemainsInVehicle() {
        UUID driverId = UUID.randomUUID();
        UUID vehicleId = UUID.randomUUID();
        UUID firstRegistrationId = UUID.randomUUID();
        UUID secondRegistrationId = UUID.randomUUID();

        List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> merged = service.mergeVehicleUsageIntervals(
                service.buildVehicleUsageIntervals(List.of(
                        supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
                        supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T23:59:59Z", null, null),
                        supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv2", "2026-04-02T00:00:00Z", null, null),
                        supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv2", "2026-04-02T12:00:00Z", null, null)
                ))
        );

        assertThat(merged).hasSize(1);
        assertThat(merged.get(0).authoritativeSource()).isEqualTo("CARD_VEHICLES_USED");
        assertThat(merged.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
        assertThat(merged.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-02T12:00:00Z"));
        assertThat(merged.get(0).sourceRowIds()).containsExactly("cv1", "cv2");
    }

    @Test
    void condensesConsecutiveCardVehiclesUsedSupportEventsToMergedBoundaryEvents() {
        UUID driverId = UUID.randomUUID();
        UUID vehicleId = UUID.randomUUID();
        UUID firstRegistrationId = UUID.randomUUID();
        UUID secondRegistrationId = UUID.randomUUID();

        List<EsperSupportEventDto> condensed = service.condenseSupportEvents(List.of(
                supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
                supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T23:59:59Z", null, null),
                supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv2", "2026-04-02T00:00:00Z", null, null),
                supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv2", "2026-04-02T12:00:00Z", null, null),
                supportEvent(driverId, vehicleId, secondRegistrationId, "POSITION", "POSITION_RECORDED", "SNAPSHOT", "CARD_POSITION", "pos1", "2026-04-02T00:05:00Z", "48.2082", "16.3738")
        ));

        assertThat(condensed).hasSize(3);
        assertThat(condensed)
                .extracting(EsperSupportEventDto::extractionCode, EsperSupportEventDto::eventType, EsperSupportEventDto::sourceRowId)
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple("CARD_VEHICLES_USED", "CARD_INSERTED", "cv1"),
                        org.assertj.core.groups.Tuple.tuple("CARD_POSITION", "POSITION_RECORDED", "pos1"),
                        org.assertj.core.groups.Tuple.tuple("CARD_VEHICLES_USED", "CARD_WITHDRAWN", "cv2")
                );
    }

    @Test
    void resolvesBoundaryVehicleGeoAndVicinityFromSupportEvents() {
        UUID driverId = UUID.randomUUID();
        UUID vehicleId = UUID.randomUUID();
        UUID registrationId = UUID.randomUUID();
        OffsetDateTime start = OffsetDateTime.parse("2026-04-01T09:00:00Z");
        OffsetDateTime end = OffsetDateTime.parse("2026-04-01T10:00:00Z");

        List<EsperSupportEventDto> supportEvents = List.of(
                supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "IW_CYCLE", "iw1", "2026-04-01T08:00:00Z", null, null),
                supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "IW_CYCLE", "iw1", "2026-04-01T12:00:00Z", null, null),
                supportEvent(driverId, vehicleId, registrationId, "POSITION", "POSITION_RECORDED", "SNAPSHOT", "VU_POSITION", "p1", "2026-04-01T09:01:00Z", "48.2082", "16.3738"),
                supportEvent(driverId, vehicleId, registrationId, "LOAD_UNLOAD", "LOAD", "SNAPSHOT", "VU_LOAD_UNLOAD", "lu1", "2026-04-01T09:03:00Z", "48.2085", "16.3740"),
                supportEvent(driverId, vehicleId, registrationId, "PLACE", "WORKING_DAY_PLACE_RECORDED", "END", "VU_PLACE", "pl1", "2026-04-01T09:59:00Z", "48.2090", "16.3750")
        );
        List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> usageIntervals = service.mergeVehicleUsageIntervals(
                service.buildVehicleUsageIntervals(supportEvents)
        );

        EnrichedDtiIntervalDto enriched = service.enrichInterval(
                new EsperDtiEnrichmentService.PureDtiInterval(
                        "DTI-1",
                        driverId,
                        "BETWEEN_SIGNIFICANT_DRIVING",
                        start,
                        end,
                        3600,
                        1,
                        OffsetDateTime.parse("2026-04-01T08:00:00Z"),
                        "d-prev",
                        "d-next"
                ),
                supportEvents,
                usageIntervals,
                new EsperDtiEnrichmentRequest(
                        "default",
                        driverId,
                        OffsetDateTime.parse("2026-04-01T00:00:00Z"),
                        OffsetDateTime.parse("2026-04-02T00:00:00Z"),
                        24,
                        7,
                        3,
                        0,
                        0,
                        EsperSourceSelectionMode.MIXED,
                        null,
                        null,
                        24 * 30,
                        180,
                        180
                )
        );

        DtiBoundaryVehicleDto beginVehicle = enriched.beginVehicle();
        assertThat(beginVehicle).isNotNull();
        assertThat(beginVehicle.vehicleId()).isEqualTo(vehicleId);
        assertThat(beginVehicle.resolutionSource()).isEqualTo("IW_CYCLE");
        assertThat(enriched.intervalKind()).isEqualTo("BETWEEN_SIGNIFICANT_DRIVING");

        DtiBoundaryPositionDto beginPosition = enriched.beginPosition();
        assertThat(beginPosition).isNotNull();
        assertThat(beginPosition.eventDomain()).isEqualTo("POSITION");
        assertThat(beginPosition.position().latitude()).isEqualByComparingTo("48.2082");

        DtiBoundaryPositionDto endPosition = enriched.endPosition();
        assertThat(endPosition).isNotNull();
        assertThat(endPosition.eventDomain()).isEqualTo("PLACE");

        List<DtiBoundaryVicinityEventDto> beginVicinity = enriched.beginVicinityEvents();
        assertThat(beginVicinity).extracting(DtiBoundaryVicinityEventDto::eventDomain)
                .contains("DRIVER_CARD", "POSITION", "LOAD_UNLOAD");
    }

    @Test
    void extractsPureDtiFromOperatingPeriodDrivingInterruptions() {
        UUID driverId = UUID.randomUUID();
        OffsetDateTime requestedFrom = OffsetDateTime.parse("2026-04-01T00:00:00Z");
        OffsetDateTime requestedTo = OffsetDateTime.parse("2026-04-02T00:00:00Z");

        EsperOperatingPeriodResultDto result = new EsperOperatingPeriodResultDto(
                "default",
                driverId,
                requestedFrom,
                requestedTo,
                requestedFrom.minusHours(24),
                requestedTo.plusHours(24),
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                1,
                1,
                7,
                3,
                0,
                0,
                EsperSourceSelectionMode.MIXED,
                EsperUnknownTreatmentMode.AS_BREAK_REST,
                EsperOperatingPeriodEngineMode.STREAM_COLLECTOR,
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(new OperatingPeriodDto(
                        4,
                        OffsetDateTime.parse("2026-04-01T06:00:00Z"),
                        OffsetDateTime.parse("2026-04-01T18:00:00Z"),
                        12 * 3600L,
                        "FLUSH",
                        List.of(),
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        new ShiftDrivingEvaluationDto(
                                3,
                                OffsetDateTime.parse("2026-04-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-04-01T16:00:00Z"),
                                activity(driverId, "2026-04-01T08:00:00Z", "2026-04-01T10:00:00Z", "d1"),
                                activity(driverId, "2026-04-01T14:00:00Z", "2026-04-01T16:00:00Z", "d2"),
                                List.of(new DrivingInterruptionDto(
                                        OffsetDateTime.parse("2026-04-01T10:00:00Z"),
                                        OffsetDateTime.parse("2026-04-01T14:00:00Z"),
                                        14400,
                                        "d1",
                                        "d2"
                                ))
                        ),
                        false
                )),
                List.of("note")
        );

        List<EsperDtiEnrichmentService.PureDtiInterval> pureDtiIntervals = service.extractPureDtiIntervals(result);

        assertThat(pureDtiIntervals).hasSize(3);
        assertThat(pureDtiIntervals).extracting(EsperDtiEnrichmentService.PureDtiInterval::intervalKind)
                .containsExactly("BEFORE_FIRST_SIGNIFICANT_DRIVING", "BETWEEN_SIGNIFICANT_DRIVING", "AFTER_LAST_SIGNIFICANT_DRIVING");
        assertThat(pureDtiIntervals.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T06:00:00Z"));
        assertThat(pureDtiIntervals.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
        assertThat(pureDtiIntervals.get(0).previousDrivingSourceRowId()).isNull();
        assertThat(pureDtiIntervals.get(0).nextDrivingSourceRowId()).isEqualTo("d1");

        assertThat(pureDtiIntervals.get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T10:00:00Z"));
        assertThat(pureDtiIntervals.get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T14:00:00Z"));
        assertThat(pureDtiIntervals.get(1).operatingPeriodNo()).isEqualTo(4);
        assertThat(pureDtiIntervals.get(1).previousDrivingSourceRowId()).isEqualTo("d1");
        assertThat(pureDtiIntervals.get(1).nextDrivingSourceRowId()).isEqualTo("d2");

        assertThat(pureDtiIntervals.get(2).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T16:00:00Z"));
        assertThat(pureDtiIntervals.get(2).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T18:00:00Z"));
        assertThat(pureDtiIntervals.get(2).previousDrivingSourceRowId()).isEqualTo("d2");
        assertThat(pureDtiIntervals.get(2).nextDrivingSourceRowId()).isNull();
    }

    private at.procon.eventhub.esperpoc.dto.ActivityIntervalDto activity(
            UUID driverId,
            String from,
            String to,
            String sourceRowId
    ) {
        return at.procon.eventhub.esperpoc.dto.ActivityIntervalDto.raw(
                driverId,
                null,
                null,
                "DRIVE",
                "DRIVER",
                "INSERTED",
                "KNOWN",
                "DRIVER_CARD",
                OffsetDateTime.parse(from),
                OffsetDateTime.parse(to),
                sourceRowId
        );
    }

    private EsperSupportEventDto supportEvent(
            UUID driverId,
            UUID vehicleId,
            UUID registrationId,
            String eventDomain,
            String eventType,
            String lifecycle,
            String extractionCode,
            String sourceRowId,
            String occurredAt,
            String latitude,
            String longitude
    ) {
        return new EsperSupportEventDto(
                UUID.randomUUID(),
                OffsetDateTime.parse(occurredAt),
                sourceRowId,
                extractionCode + ":" + sourceRowId + ":" + eventType,
                extractionCode.startsWith("VU_") || "IW_CYCLE".equals(extractionCode) ? "VEHICLE_UNIT" : "DRIVER_CARD",
                extractionCode,
                driverId,
                UUID.randomUUID(),
                vehicleId,
                registrationId,
                eventDomain,
                eventType,
                lifecycle,
                "DRIVER",
                latitude == null ? null : new BigDecimal(latitude),
                longitude == null ? null : new BigDecimal(longitude),
                null,
                null,
                null,
                null,
                "LOAD".equals(eventType) ? "LOAD" : null
        );
    }
}
