package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDrivingInterruptionInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeGeoEvidenceEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePotentialInVehicleTripInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVuCardAbsentInterval;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperGeoEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;

public final class TachographDriverWorkingTimeAdapter {

    private TachographDriverWorkingTimeAdapter() {
    }

    public static DriverWorkingTimeProcessingInput toProcessingInput(
            UUID sessionId,
            String driverKey,
            ResolvedDriverTimeline timeline,
            OffsetDateTime requestedFrom,
            OffsetDateTime requestedTo,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes,
            List<String> notes
    ) {
        return new DriverWorkingTimeProcessingInput(
                sessionId,
                driverKey,
                timeline.sourceKind(),
                timeline.loadedFrom(),
                timeline.loadedTo(),
                requestedFrom,
                requestedTo,
                significantDrivingMinutes,
                minimumRestPeriodMinutes,
                safe(timeline.activityIntervals()).stream()
                        .map(interval -> toActivityInterval(sessionId, driverKey, interval))
                        .toList(),
                safe(timeline.vehicleUsageIntervals()).stream()
                        .map(TachographDriverWorkingTimeAdapter::toVehicleUsageInterval)
                        .toList(),
                safe(timeline.supportEvents()).stream()
                        .map(TachographDriverWorkingTimeAdapter::toSupportEvidenceEvent)
                        .toList(),
                notes
        );
    }

    public static DriverWorkingTimeActivityInterval toActivityInterval(
            UUID sessionId,
            String driverKey,
            ResolvedActivityInterval interval
    ) {
        if (interval == null || interval.from() == null || interval.to() == null) {
            return null;
        }
        return new DriverWorkingTimeActivityInterval(
                sessionId,
                driverKey,
                interval.intervalId(),
                interval.activityType(),
                interval.slot(),
                interval.cardStatus(),
                interval.drivingStatus(),
                interval.registrationKey(),
                interval.vehicleKey(),
                interval.sourceKind(),
                firstSourceIntervalId(interval),
                lastSourceIntervalId(interval),
                interval.from(),
                interval.to(),
                interval.from().toEpochSecond(),
                interval.to().toEpochSecond(),
                interval.durationSeconds(),
                interval.sourceIntervalIds(),
                interval.synthetic(),
                interval.clippedToRequestedPeriod(),
                interval.level()
        );
    }

    public static DriverWorkingTimeVehicleUsageInterval toVehicleUsageInterval(ResolvedVehicleUsageInterval interval) {
        if (interval == null || interval.from() == null) {
            return null;
        }
        return new DriverWorkingTimeVehicleUsageInterval(
                interval.sessionId(),
                interval.driverKey(),
                interval.intervalId(),
                firstSourceIntervalId(interval),
                lastSourceIntervalId(interval),
                interval.from(),
                interval.to(),
                interval.from().toEpochSecond(),
                interval.to() == null ? null : interval.to().toEpochSecond(),
                interval.durationSeconds(),
                interval.odometerBeginKm(),
                interval.odometerEndKm(),
                interval.registrationKey(),
                interval.vehicleKey(),
                interval.sourceKind(),
                interval.sourceIntervalIds()
        );
    }

    public static RuntimeSupportEvidenceEvent toSupportEvidenceEvent(ExtractedSupportEvent supportEvent) {
        if (supportEvent == null || supportEvent.occurredAt() == null) {
            return null;
        }
        return new RuntimeSupportEvidenceEvent(
                supportEvent.eventId(),
                null,
                null,
                supportEvent.eventDomain(),
                supportEvent.eventType(),
                supportEvent.eventLifecycle(),
                supportEvent.driverKey(),
                supportEvent.vehicleKey(),
                supportEvent.registrationKey(),
                supportEvent.occurredAt(),
                supportEvent.occurredAt().toEpochSecond(),
                supportEvent.latitude(),
                supportEvent.longitude(),
                supportEvent.country(),
                supportEvent.region(),
                supportEvent.countryFrom(),
                supportEvent.countryTo(),
                supportEvent.operation(),
                supportEvent.odometerKm(),
                supportEvent.avgSpeedKmh(),
                supportEvent.maxSpeedKmh(),
                java.util.Map.of("rawRecordPath", supportEvent.rawRecordPath())
        );
    }

    public static TachographEsperDrivingDerivedProjectionBundle toLegacyBundle(
            DriverWorkingTimeDerivedProjectionBundle bundle
    ) {
        if (bundle == null) {
            return emptyLegacyBundle();
        }
        return new TachographEsperDrivingDerivedProjectionBundle(
                safe(bundle.drivingInterruptionIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
                safe(bundle.dailyWeeklyRestCandidateIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
                safe(bundle.dailyWeeklyRestCandidateCoverageIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyCoverage).toList(),
                safe(bundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyCoverage).toList(),
                safe(bundle.drivingInterruptionVehicleChangeIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
                safe(bundle.vuCardAbsentIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
                safe(bundle.potentialHomeOvernightStayIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyPotentialHome).toList(),
                safe(bundle.potentialInVehicleOvernightStayIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyPotentialInVehicle).toList(),
                safe(bundle.potentialInVehicleTripIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList()
        );
    }

    public static TachographEsperDrivingDerivedProjectionBundle mergeLegacyBundles(
            List<TachographEsperDrivingDerivedProjectionBundle> bundles
    ) {
        if (bundles == null || bundles.isEmpty()) {
            return emptyLegacyBundle();
        }

        List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals = new ArrayList<>();
        List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals = new ArrayList<>();
        List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
        List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
        List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals = new ArrayList<>();
        List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
        List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
        List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
        List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = new ArrayList<>();

        for (TachographEsperDrivingDerivedProjectionBundle bundle : safe(bundles)) {
            if (bundle == null) {
                continue;
            }
            drivingInterruptionIntervals.addAll(safe(bundle.drivingInterruptionIntervals()));
            dailyWeeklyRestCandidateIntervals.addAll(safe(bundle.dailyWeeklyRestCandidateIntervals()));
            dailyWeeklyRestCandidateCoverageIntervals.addAll(safe(bundle.dailyWeeklyRestCandidateCoverageIntervals()));
            unclassifiedDailyWeeklyRestCandidateCoverageIntervals.addAll(safe(bundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals()));
            drivingInterruptionVehicleChangeIntervals.addAll(safe(bundle.drivingInterruptionVehicleChangeIntervals()));
            vuCardAbsentIntervals.addAll(safe(bundle.vuCardAbsentIntervals()));
            potentialHomeOvernightStayIntervals.addAll(safe(bundle.potentialHomeOvernightStayIntervals()));
            potentialInVehicleOvernightStayIntervals.addAll(safe(bundle.potentialInVehicleOvernightStayIntervals()));
            potentialInVehicleTripIntervals.addAll(safe(bundle.potentialInVehicleTripIntervals()));
        }

        return new TachographEsperDrivingDerivedProjectionBundle(
                sortDrivingInterruptionIntervals(drivingInterruptionIntervals),
                sortDrivingInterruptionIntervals(dailyWeeklyRestCandidateIntervals),
                sortCoverageIntervals(dailyWeeklyRestCandidateCoverageIntervals),
                sortCoverageIntervals(unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
                sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
                sortVuCardAbsentIntervals(vuCardAbsentIntervals),
                sortPotentialHomeIntervals(potentialHomeOvernightStayIntervals),
                sortPotentialInVehicleIntervals(potentialInVehicleOvernightStayIntervals),
                sortPotentialInVehicleTripIntervals(potentialInVehicleTripIntervals)
        );
    }

    public static TachographEsperDrivingDerivedProjectionBundle emptyLegacyBundle() {
        return new TachographEsperDrivingDerivedProjectionBundle(
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of(),
                List.of()
        );
    }

    private static TachographEsperDrivingInterruptionIntervalEvent toLegacy(
            DriverWorkingTimeDrivingInterruptionInterval value
    ) {
        return new TachographEsperDrivingInterruptionIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.previousDrivingSourceIntervalId(),
                value.nextDrivingSourceIntervalId(),
                value.previousRegistrationKey(),
                value.nextRegistrationKey(),
                value.previousVehicleKey(),
                value.nextVehicleKey()
        );
    }

    private static TachographEsperVuCardAbsentIntervalEvent toLegacy(DriverWorkingTimeVuCardAbsentInterval value) {
        return new TachographEsperVuCardAbsentIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.previousUsageIntervalId(),
                value.nextUsageIntervalId(),
                value.previousRegistrationKey(),
                value.nextRegistrationKey(),
                value.previousVehicleKey(),
                value.nextVehicleKey()
        );
    }

    private static TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent toLegacyCoverage(
            DriverWorkingTimeRestCoverageInterval value
    ) {
        return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.cardAbsentDurationSeconds(),
                value.cardAbsentCoveragePercent(),
                value.previousDrivingSourceIntervalId(),
                value.nextDrivingSourceIntervalId(),
                value.previousRegistrationKey(),
                value.nextRegistrationKey(),
                value.previousVehicleKey(),
                value.nextVehicleKey(),
                value.beginBoundaryOdometerKm(),
                value.endBoundaryOdometerKm(),
                value.beginGeoEventId(),
                value.beginGeoEventDomain(),
                value.beginGeoOccurredAt(),
                value.beginLatitude(),
                value.beginLongitude(),
                value.beginGeoDistanceSeconds(),
                value.beginGeoOdometerKm(),
                value.endGeoEventId(),
                value.endGeoEventDomain(),
                value.endGeoOccurredAt(),
                value.endLatitude(),
                value.endLongitude(),
                value.endGeoDistanceSeconds(),
                value.endGeoOdometerKm(),
                value.geoEvidenceMovementMeters(),
                value.geoEvidenceMovementCategory(),
                toLegacy(value.beginGeoEvent()),
                toLegacy(value.endGeoEvent())
        );
    }

    private static TachographEsperPotentialHomeOvernightStayIntervalEvent toLegacyPotentialHome(
            DriverWorkingTimeRestCoverageInterval value
    ) {
        return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.cardAbsentDurationSeconds(),
                value.cardAbsentCoveragePercent(),
                value.previousDrivingSourceIntervalId(),
                value.nextDrivingSourceIntervalId(),
                value.previousRegistrationKey(),
                value.nextRegistrationKey(),
                value.previousVehicleKey(),
                value.nextVehicleKey(),
                value.beginBoundaryOdometerKm(),
                value.endBoundaryOdometerKm(),
                value.beginGeoEventId(),
                value.beginGeoEventDomain(),
                value.beginGeoOccurredAt(),
                value.beginLatitude(),
                value.beginLongitude(),
                value.beginGeoDistanceSeconds(),
                value.beginGeoOdometerKm(),
                value.endGeoEventId(),
                value.endGeoEventDomain(),
                value.endGeoOccurredAt(),
                value.endLatitude(),
                value.endLongitude(),
                value.endGeoDistanceSeconds(),
                value.endGeoOdometerKm(),
                value.geoEvidenceMovementMeters(),
                value.geoEvidenceMovementCategory(),
                toLegacy(value.beginGeoEvent()),
                toLegacy(value.endGeoEvent())
        );
    }

    private static TachographEsperPotentialInVehicleOvernightStayIntervalEvent toLegacyPotentialInVehicle(
            DriverWorkingTimeRestCoverageInterval value
    ) {
        return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.cardAbsentDurationSeconds(),
                value.cardAbsentCoveragePercent(),
                value.previousDrivingSourceIntervalId(),
                value.nextDrivingSourceIntervalId(),
                value.previousRegistrationKey(),
                value.nextRegistrationKey(),
                value.previousVehicleKey(),
                value.nextVehicleKey(),
                value.beginBoundaryOdometerKm(),
                value.endBoundaryOdometerKm(),
                value.beginGeoEventId(),
                value.beginGeoEventDomain(),
                value.beginGeoOccurredAt(),
                value.beginLatitude(),
                value.beginLongitude(),
                value.beginGeoDistanceSeconds(),
                value.beginGeoOdometerKm(),
                value.endGeoEventId(),
                value.endGeoEventDomain(),
                value.endGeoOccurredAt(),
                value.endLatitude(),
                value.endLongitude(),
                value.endGeoDistanceSeconds(),
                value.endGeoOdometerKm(),
                value.geoEvidenceMovementMeters(),
                value.geoEvidenceMovementCategory(),
                toLegacy(value.beginGeoEvent()),
                toLegacy(value.endGeoEvent())
        );
    }

    private static TachographEsperPotentialInVehicleTripIntervalEvent toLegacy(
            DriverWorkingTimePotentialInVehicleTripInterval value
    ) {
        return new TachographEsperPotentialInVehicleTripIntervalEvent(
                value.sessionId(),
                value.driverKey(),
                value.startedAt(),
                value.endedAt(),
                value.durationSeconds(),
                value.registrationKey(),
                value.vehicleKey(),
                value.containedPotentialInVehicleOvernightStayIntervalCount(),
                value.containedPotentialInVehicleOvernightStayDurationSeconds(),
                value.containedCardAbsentDurationSeconds(),
                value.firstPotentialInVehicleOvernightStayStartedAt(),
                value.lastPotentialInVehicleOvernightStayEndedAt(),
                value.firstPreviousDrivingSourceIntervalId(),
                value.lastNextDrivingSourceIntervalId(),
                safe(value.potentialInVehicleOvernightStayIntervals()).stream()
                        .map(TachographDriverWorkingTimeAdapter::toLegacyPotentialInVehicle)
                        .toList()
        );
    }

    private static TachographEsperGeoEvidenceEvent toLegacy(DriverWorkingTimeGeoEvidenceEvent value) {
        if (value == null) {
            return null;
        }
        return new TachographEsperGeoEvidenceEvent(
                value.eventId(),
                value.eventDomain(),
                value.occurredAt(),
                value.latitude(),
                value.longitude(),
                value.distanceSeconds(),
                value.odometerKm()
        );
    }

    private static List<TachographEsperDrivingInterruptionIntervalEvent> sortDrivingInterruptionIntervals(
            List<TachographEsperDrivingInterruptionIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperDrivingInterruptionIntervalEvent::startedAt)
                        .thenComparing(TachographEsperDrivingInterruptionIntervalEvent::endedAt))
                .toList();
    }

    private static List<TachographEsperVuCardAbsentIntervalEvent> sortVuCardAbsentIntervals(
            List<TachographEsperVuCardAbsentIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperVuCardAbsentIntervalEvent::startedAt)
                        .thenComparing(TachographEsperVuCardAbsentIntervalEvent::endedAt))
                .toList();
    }

    private static List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> sortCoverageIntervals(
            List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::startedAt)
                        .thenComparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::endedAt))
                .toList();
    }

    private static List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeIntervals(
            List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
                        .thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
                .toList();
    }

    private static List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> sortPotentialInVehicleIntervals(
            List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
                        .thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
                .toList();
    }

    private static List<TachographEsperPotentialInVehicleTripIntervalEvent> sortPotentialInVehicleTripIntervals(
            List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals
    ) {
        return safe(intervals).stream()
                .sorted(Comparator.comparing(TachographEsperPotentialInVehicleTripIntervalEvent::startedAt)
                        .thenComparing(TachographEsperPotentialInVehicleTripIntervalEvent::endedAt))
                .toList();
    }

    private static String firstSourceIntervalId(ResolvedActivityInterval interval) {
        return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getFirst();
    }

    private static String lastSourceIntervalId(ResolvedActivityInterval interval) {
        return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getLast();
    }

    private static String firstSourceIntervalId(ResolvedVehicleUsageInterval interval) {
        return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getFirst();
    }

    private static String lastSourceIntervalId(ResolvedVehicleUsageInterval interval) {
        return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getLast();
    }

    private static <T> List<T> safe(List<T> values) {
        return values == null ? List.of() : values;
    }
}
