package at.procon.eventhub.esperpoc.service;

import at.procon.eventhub.dto.GeoPointDto;
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.EnrichedDtiIntervalDto;
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentRequest;
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentResultDto;
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest;
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto;
import at.procon.eventhub.esperpoc.dto.EsperSupportEventDto;
import at.procon.eventhub.esperpoc.persistence.EsperPocDtiEnrichmentRepository;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.springframework.stereotype.Service;

@Service
public class EsperDtiEnrichmentService {

    private static final Set<String> VEHICLE_INTERVAL_EXTRACTION_CODES = Set.of("IW_CYCLE", "CARD_VEHICLES_USED");
    private static final int PRECEDING_DRIVER_CARD_EVENT_COUNT = 50;

    private final EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService;
    private final EsperPocDtiEnrichmentRepository enrichmentRepository;

    public EsperDtiEnrichmentService(
            EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService,
            EsperPocDtiEnrichmentRepository enrichmentRepository
    ) {
        this.operatingPeriodEvaluationService = operatingPeriodEvaluationService;
        this.enrichmentRepository = enrichmentRepository;
    }

    public EsperDtiEnrichmentResultDto evaluate(EsperDtiEnrichmentRequest request) {
        EsperOperatingPeriodRequest operatingRequest = new EsperOperatingPeriodRequest(
                request.tenantKey(),
                request.driverId(),
                request.occurredFrom(),
                request.occurredTo(),
                request.guardHours(),
                request.operatingSplitIdleHours(),
                request.significantDrivingMinutes(),
                request.mergeGapSeconds(),
                request.gapDetectionToleranceSeconds(),
                request.sourceSelectionMode(),
                request.unknownTreatmentMode(),
                request.engineMode()
        );
        EsperOperatingPeriodResultDto pureDtiResult = operatingPeriodEvaluationService.evaluate(operatingRequest);

        OffsetDateTime supportFrom = request.occurredFrom().minusHours(request.vehicleEvidenceLookbackHours());
        OffsetDateTime supportTo = request.occurredTo().plusHours(Math.max(request.guardHours(), 24));
        List<EsperSupportEventDto> rawSupportEvents = enrichmentRepository.findDriverSupportEvents(
                request.tenantKey(),
                request.driverId(),
                supportFrom,
                supportTo,
                PRECEDING_DRIVER_CARD_EVENT_COUNT,
                request.sourceSelectionMode()
        );
        List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = mergeVehicleUsageIntervals(buildVehicleUsageIntervals(rawSupportEvents));
        List<EsperSupportEventDto> supportEvents = condenseSupportEvents(rawSupportEvents);
        List<PureDtiInterval> pureDtiIntervals = extractPureDtiIntervals(pureDtiResult);
        List<EnrichedDtiIntervalDto> enrichedIntervals = pureDtiIntervals.stream()
                .map(interval -> enrichInterval(interval, supportEvents, vehicleUsageIntervals, request))
                .toList();

        return new EsperDtiEnrichmentResultDto(
                request.tenantKey(),
                request.driverId(),
                pureDtiResult.requestedFrom(),
                pureDtiResult.requestedTo(),
                pureDtiResult.loadedFrom(),
                pureDtiResult.loadedTo(),
                supportFrom,
                supportTo,
                pureDtiIntervals.size(),
                supportEvents.size(),
                vehicleUsageIntervals.size(),
                request.geoSearchWindowMinutes(),
                request.vicinityWindowMinutes(),
                request.vehicleEvidenceLookbackHours(),
                enrichedIntervals,
                notes(request)
        );
    }

    EnrichedDtiIntervalDto enrichInterval(
            PureDtiInterval interval,
            List<EsperSupportEventDto> supportEvents,
            List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
            EsperDtiEnrichmentRequest request
    ) {
        DtiBoundaryVehicleDto beginVehicle = resolveBoundaryVehicle(interval.startedAt(), vehicleUsageIntervals);
        DtiBoundaryVehicleDto endVehicle = resolveBoundaryVehicle(interval.endedAt(), vehicleUsageIntervals);
        DtiBoundaryPositionDto beginPosition = resolveBoundaryPosition(
                interval.startedAt(),
                beginVehicle,
                supportEvents,
                request.geoSearchWindowMinutes()
        );
        DtiBoundaryPositionDto endPosition = resolveBoundaryPosition(
                interval.endedAt(),
                endVehicle,
                supportEvents,
                request.geoSearchWindowMinutes()
        );
        List<DtiBoundaryVicinityEventDto> beginVicinity = resolveBoundaryVicinityEvents(
                interval.startedAt(),
                supportEvents,
                request.vicinityWindowMinutes()
        );
        List<DtiBoundaryVicinityEventDto> endVicinity = resolveBoundaryVicinityEvents(
                interval.endedAt(),
                supportEvents,
                request.vicinityWindowMinutes()
        );
        return new EnrichedDtiIntervalDto(
                interval.dtiId(),
                interval.driverId(),
                interval.intervalKind(),
                interval.startedAt(),
                interval.endedAt(),
                interval.durationSeconds(),
                interval.operatingPeriodNo(),
                interval.operatingPeriodStartedAt(),
                interval.previousDrivingSourceRowId(),
                interval.nextDrivingSourceRowId(),
                beginVehicle,
                endVehicle,
                beginPosition,
                endPosition,
                beginVicinity,
                endVicinity
        );
    }

    List<PureDtiInterval> extractPureDtiIntervals(EsperOperatingPeriodResultDto result) {
        if (result == null || result.operatingPeriods() == null || result.operatingPeriods().isEmpty()) {
            return List.of();
        }
        List<PureDtiInterval> intervals = new ArrayList<>();
        for (OperatingPeriodDto operatingPeriod : result.operatingPeriods()) {
            if (operatingPeriod.drivingTimeInterruptionEvaluation() == null
                    || operatingPeriod.drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods() == null) {
                continue;
            }
            if (operatingPeriod.drivingTimeInterruptionEvaluation().departureAt() != null
                    && operatingPeriod.startedAt().isBefore(operatingPeriod.drivingTimeInterruptionEvaluation().departureAt())) {
                intervals.add(new PureDtiInterval(
                        "DTI-PRE-" + operatingPeriod.operatingPeriodNo() + "-" + operatingPeriod.startedAt().toInstant().toEpochMilli(),
                        result.driverId(),
                        "BEFORE_FIRST_SIGNIFICANT_DRIVING",
                        operatingPeriod.startedAt(),
                        operatingPeriod.drivingTimeInterruptionEvaluation().departureAt(),
                        Duration.between(operatingPeriod.startedAt(), operatingPeriod.drivingTimeInterruptionEvaluation().departureAt()).getSeconds(),
                        operatingPeriod.operatingPeriodNo(),
                        operatingPeriod.startedAt(),
                        null,
                        operatingPeriod.drivingTimeInterruptionEvaluation().firstSignificantDrivingPeriod() == null
                                ? null
                                : operatingPeriod.drivingTimeInterruptionEvaluation().firstSignificantDrivingPeriod().sourceRowId()
                ));
            }
            operatingPeriod.drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods()
                    .forEach(interruption -> intervals.add(new PureDtiInterval(
                            "DTI-" + operatingPeriod.operatingPeriodNo() + "-" + interruption.from().toInstant().toEpochMilli(),
                            result.driverId(),
                            "BETWEEN_SIGNIFICANT_DRIVING",
                            interruption.from(),
                            interruption.to(),
                            interruption.durationSeconds(),
                            operatingPeriod.operatingPeriodNo(),
                            operatingPeriod.startedAt(),
                            interruption.previousDrivingSourceRowId(),
                            interruption.nextDrivingSourceRowId()
                    )));
            if (operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt() != null
                    && operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt().isBefore(operatingPeriod.endedAt())) {
                intervals.add(new PureDtiInterval(
                        "DTI-POST-" + operatingPeriod.operatingPeriodNo() + "-" + operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt().toInstant().toEpochMilli(),
                        result.driverId(),
                        "AFTER_LAST_SIGNIFICANT_DRIVING",
                        operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt(),
                        operatingPeriod.endedAt(),
                        Duration.between(operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt(), operatingPeriod.endedAt()).getSeconds(),
                        operatingPeriod.operatingPeriodNo(),
                        operatingPeriod.startedAt(),
                        operatingPeriod.drivingTimeInterruptionEvaluation().lastSignificantDrivingPeriod() == null
                                ? null
                                : operatingPeriod.drivingTimeInterruptionEvaluation().lastSignificantDrivingPeriod().sourceRowId(),
                        null
                ));
            }
        }
        return intervals;
    }

    List<ResolvedVehicleUsageInterval> buildVehicleUsageIntervals(List<EsperSupportEventDto> supportEvents) {
        record VehicleIntervalSeed(
                EsperSupportEventDto insertEvent,
                EsperSupportEventDto withdrawEvent
        ) {
        }
        java.util.Map<String, VehicleIntervalSeed> bySourceRow = new java.util.LinkedHashMap<>();
        supportEvents.stream()
                .filter(event -> "DRIVER_CARD".equals(event.eventDomain()))
                .filter(event -> VEHICLE_INTERVAL_EXTRACTION_CODES.contains(event.extractionCode()))
                .filter(event -> "CARD_INSERTED".equals(event.eventType()) || "CARD_WITHDRAWN".equals(event.eventType()))
                .filter(event -> event.sourceRowId() != null)
                .forEach(event -> {
                    String key = event.extractionCode() + ":" + event.sourceRowId();
                    VehicleIntervalSeed current = bySourceRow.get(key);
                    EsperSupportEventDto insert = current == null ? null : current.insertEvent();
                    EsperSupportEventDto withdraw = current == null ? null : current.withdrawEvent();
                    if ("CARD_INSERTED".equals(event.eventType())) {
                        if (insert == null || event.occurredAt().isBefore(insert.occurredAt())) {
                            insert = event;
                        }
                    } else if (withdraw == null || event.occurredAt().isAfter(withdraw.occurredAt())) {
                        withdraw = event;
                    }
                    bySourceRow.put(key, new VehicleIntervalSeed(insert, withdraw));
                });

        List<ResolvedVehicleUsageInterval> result = new ArrayList<>();
        for (VehicleIntervalSeed seed : bySourceRow.values()) {
            EsperSupportEventDto anchor = seed.insertEvent() != null ? seed.insertEvent() : seed.withdrawEvent();
            if (anchor == null || anchor.vehicleId() == null) {
                continue;
            }
            OffsetDateTime startedAt = seed.insertEvent() == null ? null : seed.insertEvent().occurredAt();
            OffsetDateTime endedAt = seed.withdrawEvent() == null ? null : seed.withdrawEvent().occurredAt();
            if (startedAt == null && endedAt == null) {
                continue;
            }
            result.add(new ResolvedVehicleUsageInterval(
                    anchor.driverId(),
                    anchor.driverCardId(),
                    anchor.vehicleId(),
                    anchor.vehicleRegistrationId(),
                    anchor.extractionCode(),
                    startedAt,
                    endedAt,
                    sourcePriority(anchor.extractionCode()),
                    sourceConfidence(anchor.extractionCode()),
                    collectSourceRowIds(seed.insertEvent(), seed.withdrawEvent())
            ));
        }
        return result.stream()
                .sorted(Comparator.comparing(ResolvedVehicleUsageInterval::startedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
                        .thenComparing(ResolvedVehicleUsageInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
                .toList();
    }

    List<ResolvedVehicleUsageInterval> mergeVehicleUsageIntervals(List<ResolvedVehicleUsageInterval> intervals) {
        if (intervals.isEmpty()) {
            return List.of();
        }
        List<ResolvedVehicleUsageInterval> sorted = intervals.stream()
                .sorted(Comparator.comparing(ResolvedVehicleUsageInterval::startedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
                        .thenComparing(ResolvedVehicleUsageInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
                .toList();
        List<ResolvedVehicleUsageInterval> merged = new ArrayList<>();
        ResolvedVehicleUsageInterval current = null;
        for (ResolvedVehicleUsageInterval next : sorted) {
            if (current == null) {
                current = next;
                continue;
            }
            if (canMerge(current, next)) {
                current = current.merge(next);
            } else {
                merged.add(current);
                current = next;
            }
        }
        if (current != null) {
            merged.add(current);
        }
        return merged;
    }

    List<EsperSupportEventDto> condenseSupportEvents(List<EsperSupportEventDto> supportEvents) {
        if (supportEvents == null || supportEvents.isEmpty()) {
            return List.of();
        }
        record VehicleIntervalSeed(
                EsperSupportEventDto insertEvent,
                EsperSupportEventDto withdrawEvent
        ) {
        }
        java.util.Map<String, VehicleIntervalSeed> seedsByKey = new java.util.LinkedHashMap<>();
        supportEvents.stream()
                .filter(event -> "DRIVER_CARD".equals(event.eventDomain()))
                .filter(event -> "CARD_VEHICLES_USED".equals(event.extractionCode()))
                .filter(event -> "CARD_INSERTED".equals(event.eventType()) || "CARD_WITHDRAWN".equals(event.eventType()))
                .filter(event -> event.sourceRowId() != null)
                .forEach(event -> {
                    String key = event.extractionCode() + ":" + event.sourceRowId();
                    VehicleIntervalSeed current = seedsByKey.get(key);
                    EsperSupportEventDto insert = current == null ? null : current.insertEvent();
                    EsperSupportEventDto withdraw = current == null ? null : current.withdrawEvent();
                    if ("CARD_INSERTED".equals(event.eventType())) {
                        if (insert == null || event.occurredAt().isBefore(insert.occurredAt())) {
                            insert = event;
                        }
                    } else if (withdraw == null || event.occurredAt().isAfter(withdraw.occurredAt())) {
                        withdraw = event;
                    }
                    seedsByKey.put(key, new VehicleIntervalSeed(insert, withdraw));
                });

        List<ResolvedVehicleUsageInterval> mergedCardVehicleIntervals = mergeVehicleUsageIntervals(
                buildVehicleUsageIntervals(supportEvents).stream()
                        .filter(interval -> "CARD_VEHICLES_USED".equals(interval.authoritativeSource()))
                        .toList()
        );
        Set<UUID> keptDriverCardEventIds = new LinkedHashSet<>();
        for (ResolvedVehicleUsageInterval interval : mergedCardVehicleIntervals) {
            EsperSupportEventDto earliestInsert = null;
            EsperSupportEventDto latestWithdraw = null;
            for (String sourceRowId : interval.sourceRowIds()) {
                VehicleIntervalSeed seed = seedsByKey.get("CARD_VEHICLES_USED:" + sourceRowId);
                if (seed == null) {
                    continue;
                }
                if (seed.insertEvent() != null
                        && (earliestInsert == null || seed.insertEvent().occurredAt().isBefore(earliestInsert.occurredAt()))) {
                    earliestInsert = seed.insertEvent();
                }
                if (seed.withdrawEvent() != null
                        && (latestWithdraw == null || seed.withdrawEvent().occurredAt().isAfter(latestWithdraw.occurredAt()))) {
                    latestWithdraw = seed.withdrawEvent();
                }
            }
            if (earliestInsert != null) {
                keptDriverCardEventIds.add(earliestInsert.eventId());
            }
            if (latestWithdraw != null) {
                keptDriverCardEventIds.add(latestWithdraw.eventId());
            }
        }

        return supportEvents.stream()
                .filter(event -> !("DRIVER_CARD".equals(event.eventDomain())
                        && "CARD_VEHICLES_USED".equals(event.extractionCode())
                        && !keptDriverCardEventIds.contains(event.eventId())))
                .sorted(Comparator.comparing(EsperSupportEventDto::occurredAt)
                        .thenComparing(EsperSupportEventDto::lifecycle, Comparator.nullsLast(String::compareTo))
                        .thenComparing(EsperSupportEventDto::eventDomain, Comparator.nullsLast(String::compareTo))
                        .thenComparing(EsperSupportEventDto::eventType, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    DtiBoundaryVehicleDto resolveBoundaryVehicle(
            OffsetDateTime boundary,
            List<ResolvedVehicleUsageInterval> intervals
    ) {
        return intervals.stream()
                .map(interval -> intervalCandidate(boundary, interval))
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(VehicleBoundaryCandidate::score).reversed()
                        .thenComparing(VehicleBoundaryCandidate::deltaSeconds)
                        .thenComparing(candidate -> candidate.interval.startedAt(), Comparator.nullsLast(Comparator.reverseOrder())))
                .map(candidate -> new DtiBoundaryVehicleDto(
                        candidate.interval.vehicleId(),
                        candidate.interval.vehicleRegistrationId(),
                        null,
                        candidate.interval.authoritativeSource(),
                        candidate.interval.confidence(),
                        candidate.interval.startedAt(),
                        candidate.interval.endedAt(),
                        candidate.interval.sourceRowIds()
                ))
                .findFirst()
                .orElse(null);
    }

    DtiBoundaryPositionDto resolveBoundaryPosition(
            OffsetDateTime boundary,
            DtiBoundaryVehicleDto boundaryVehicle,
            List<EsperSupportEventDto> supportEvents,
            int searchWindowMinutes
    ) {
        long maxDeltaSeconds = Duration.ofMinutes(searchWindowMinutes).getSeconds();
        return supportEvents.stream()
                .filter(this::hasPosition)
                .filter(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()) <= maxDeltaSeconds)
                .sorted(Comparator
                        .comparing((EsperSupportEventDto event) -> vehicleMatch(event, boundaryVehicle)).reversed()
                        .thenComparing(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()))
                        .thenComparing((EsperSupportEventDto event) -> geoDomainPriority(event.eventDomain()), Comparator.reverseOrder())
                        .thenComparing(EsperSupportEventDto::occurredAt, Comparator.reverseOrder()))
                .map(event -> toBoundaryPosition(boundary, boundaryVehicle, event))
                .findFirst()
                .orElse(null);
    }

    List<DtiBoundaryVicinityEventDto> resolveBoundaryVicinityEvents(
            OffsetDateTime boundary,
            List<EsperSupportEventDto> supportEvents,
            int vicinityWindowMinutes
    ) {
        long maxDeltaSeconds = Duration.ofMinutes(vicinityWindowMinutes).getSeconds();
        return supportEvents.stream()
                .filter(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()) <= maxDeltaSeconds)
                .sorted(Comparator
                        .comparing((EsperSupportEventDto event) -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()))
                        .thenComparing((EsperSupportEventDto event) -> supportEventPriority(event.eventDomain()), Comparator.reverseOrder())
                        .thenComparing(EsperSupportEventDto::occurredAt, Comparator.reverseOrder()))
                .limit(20)
                .map(event -> new DtiBoundaryVicinityEventDto(
                        event.occurredAt(),
                        event.eventDomain(),
                        event.eventType(),
                        event.lifecycle(),
                        event.sourceKind(),
                        event.extractionCode(),
                        event.vehicleId(),
                        event.vehicleRegistrationId(),
                        toGeoPoint(event),
                        event.country(),
                        event.region(),
                        event.countryFrom(),
                        event.countryTo(),
                        event.operation(),
                        Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()),
                        event.sourceRowId()
                ))
                .toList();
    }

    private VehicleBoundaryCandidate intervalCandidate(OffsetDateTime boundary, ResolvedVehicleUsageInterval interval) {
        if (interval.startedAt() == null) {
            return null;
        }
        boolean covers = !interval.startedAt().isAfter(boundary) && (interval.endedAt() == null || interval.endedAt().isAfter(boundary) || interval.endedAt().isEqual(boundary));
        long deltaSeconds;
        int baseScore;
        if (covers) {
            deltaSeconds = 0;
            baseScore = interval.priority() + 200;
        } else if (interval.endedAt() != null && interval.endedAt().isBefore(boundary)) {
            deltaSeconds = Duration.between(interval.endedAt(), boundary).getSeconds();
            baseScore = interval.priority();
        } else {
            deltaSeconds = Math.abs(Duration.between(boundary, interval.startedAt()).getSeconds());
            baseScore = interval.priority() - 25;
        }
        if (deltaSeconds > Duration.ofHours(24).getSeconds()) {
            return null;
        }
        return new VehicleBoundaryCandidate(interval, baseScore, deltaSeconds);
    }

    private boolean canMerge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
        if (!Objects.equals(left.driverId(), right.driverId())) {
            return false;
        }
        if (!Objects.equals(left.vehicleId(), right.vehicleId())) {
            return false;
        }
        boolean midnightCardVehiclesUsedContinuation =
                "CARD_VEHICLES_USED".equals(left.authoritativeSource())
                        && "CARD_VEHICLES_USED".equals(right.authoritativeSource())
                        && left.endedAt() != null
                        && right.startedAt() != null
                        && Duration.between(left.endedAt(), right.startedAt()).getSeconds() == 1;
        if (!Objects.equals(left.vehicleRegistrationId(), right.vehicleRegistrationId())
                && !midnightCardVehiclesUsedContinuation) {
            return false;
        }
        if (left.endedAt() == null || right.startedAt() == null) {
            return true;
        }
        return !right.startedAt().isAfter(left.endedAt().plusSeconds(60));
    }

    private boolean hasPosition(EsperSupportEventDto event) {
        return event.latitude() != null && event.longitude() != null;
    }

    private boolean vehicleMatch(EsperSupportEventDto event, DtiBoundaryVehicleDto boundaryVehicle) {
        if (boundaryVehicle == null) {
            return false;
        }
        return Objects.equals(event.vehicleId(), boundaryVehicle.vehicleId())
                || (boundaryVehicle.vehicleRegistrationId() != null
                && Objects.equals(event.vehicleRegistrationId(), boundaryVehicle.vehicleRegistrationId()));
    }

    private DtiBoundaryPositionDto toBoundaryPosition(
            OffsetDateTime boundary,
            DtiBoundaryVehicleDto boundaryVehicle,
            EsperSupportEventDto event
    ) {
        int confidence = geoDomainPriority(event.eventDomain()) + (vehicleMatch(event, boundaryVehicle) ? 100 : 0);
        return new DtiBoundaryPositionDto(
                event.occurredAt(),
                toGeoPoint(event),
                event.eventDomain(),
                event.eventType(),
                event.sourceKind(),
                event.extractionCode(),
                event.vehicleId(),
                event.vehicleRegistrationId(),
                event.country(),
                event.region(),
                event.countryFrom(),
                event.countryTo(),
                event.operation(),
                Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()),
                confidence,
                event.sourceRowId()
        );
    }

    private GeoPointDto toGeoPoint(EsperSupportEventDto event) {
        if (!hasPosition(event)) {
            return null;
        }
        return new GeoPointDto(event.latitude(), event.longitude());
    }

    private int geoDomainPriority(String eventDomain) {
        return switch (eventDomain) {
            case "POSITION" -> 400;
            case "PLACE" -> 350;
            case "BORDER_CROSSING" -> 300;
            case "LOAD_UNLOAD" -> 250;
            case "SPECIFIC_CONDITION" -> 200;
            default -> 100;
        };
    }

    private int supportEventPriority(String eventDomain) {
        return switch (eventDomain) {
            case "DRIVER_CARD" -> 500;
            case "POSITION" -> 450;
            case "PLACE" -> 400;
            case "BORDER_CROSSING" -> 350;
            case "LOAD_UNLOAD" -> 300;
            case "SPECIFIC_CONDITION" -> 250;
            case "SPEEDING" -> 200;
            default -> 100;
        };
    }

    private int sourcePriority(String extractionCode) {
        return "IW_CYCLE".equals(extractionCode) ? 1000 : "CARD_VEHICLES_USED".equals(extractionCode) ? 900 : 100;
    }

    private int sourceConfidence(String extractionCode) {
        return "IW_CYCLE".equals(extractionCode) ? 100 : "CARD_VEHICLES_USED".equals(extractionCode) ? 90 : 50;
    }

    private List<String> collectSourceRowIds(EsperSupportEventDto first, EsperSupportEventDto second) {
        Set<String> ids = new LinkedHashSet<>();
        if (first != null && first.sourceRowId() != null) {
            ids.add(first.sourceRowId());
        }
        if (second != null && second.sourceRowId() != null) {
            ids.add(second.sourceRowId());
        }
        return List.copyOf(ids);
    }

    private List<String> notes(EsperDtiEnrichmentRequest request) {
        return List.of(
                "Pure DTI intervals come from operating-period driving interruption evaluation, including before-first and after-last significant driving intervals.",
                "Vehicle evidence prefers IW_CYCLE over CARD_VEHICLES_USED when both describe the same time span.",
                "Boundary geo selection prefers POSITION, then PLACE, then BORDER_CROSSING and LOAD_UNLOAD.",
                "Vicinity events include non-activity tachograph events around each DTI boundary.",
                "Vehicle lookback window is " + request.vehicleEvidenceLookbackHours() + " hours."
        );
    }

    record ResolvedVehicleUsageInterval(
            UUID driverId,
            UUID driverCardId,
            UUID vehicleId,
            UUID vehicleRegistrationId,
            String authoritativeSource,
            OffsetDateTime startedAt,
            OffsetDateTime endedAt,
            int priority,
            int confidence,
            List<String> sourceRowIds
    ) {
        ResolvedVehicleUsageInterval merge(ResolvedVehicleUsageInterval other) {
            OffsetDateTime mergedStart = startedAt == null ? other.startedAt : other.startedAt == null ? startedAt
                    : startedAt.isBefore(other.startedAt) ? startedAt : other.startedAt;
            OffsetDateTime mergedEnd;
            if (endedAt == null || other.endedAt == null) {
                mergedEnd = null;
            } else {
                mergedEnd = endedAt.isAfter(other.endedAt) ? endedAt : other.endedAt;
            }
            String source = priority >= other.priority ? authoritativeSource : other.authoritativeSource;
            int mergedPriority = Math.max(priority, other.priority);
            int mergedConfidence = Math.max(confidence, other.confidence);
            LinkedHashSet<String> mergedIds = new LinkedHashSet<>(sourceRowIds);
            mergedIds.addAll(other.sourceRowIds);
            return new ResolvedVehicleUsageInterval(
                    driverId,
                    driverCardId != null ? driverCardId : other.driverCardId,
                    vehicleId,
                    vehicleRegistrationId != null ? vehicleRegistrationId : other.vehicleRegistrationId,
                    source,
                    mergedStart,
                    mergedEnd,
                    mergedPriority,
                    mergedConfidence,
                    List.copyOf(mergedIds)
            );
        }
    }

    record VehicleBoundaryCandidate(
            ResolvedVehicleUsageInterval interval,
            int score,
            long deltaSeconds
    ) {
    }

    record PureDtiInterval(
            String dtiId,
            UUID driverId,
            String intervalKind,
            OffsetDateTime startedAt,
            OffsetDateTime endedAt,
            long durationSeconds,
            long operatingPeriodNo,
            OffsetDateTime operatingPeriodStartedAt,
            String previousDrivingSourceRowId,
            String nextDrivingSourceRowId
    ) {
    }
}
