package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
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.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
import java.time.OffsetDateTime;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.stereotype.Component;

/**
 * Builds EventHub point events directly from the extracted tachograph source records.
 *
 * <p>This builder intentionally does not run the normal {@link DriverTimelineBuilder} interval
 * resolution/merging path. It only wraps each raw source interval as a START/END or
 * INSERT/WITHDRAW point-event pair. Any subsequent pairing, coalescing, or derived interval logic
 * belongs to the event-input EPL projection pipeline.</p>
 */
@Component
public class RawSourceDriverTimelineEventBuilder {

    private final IntervalBackedDriverTimelineEventBuilder intervalEventBuilder;

    public RawSourceDriverTimelineEventBuilder(IntervalBackedDriverTimelineEventBuilder intervalEventBuilder) {
        this.intervalEventBuilder = intervalEventBuilder;
    }

    public TachographTimelineEventBundle buildRawEventBundle(
            TachographFileSession session,
            DriverExtractionSession driverSession
    ) {
        if (session == null || driverSession == null) {
            return new TachographTimelineEventBundle(List.of(), List.of(), List.of());
        }
        return intervalEventBuilder.buildEventBundle(session, driverSession, buildRawTimeline(session, driverSession));
    }

    public ResolvedDriverTimeline buildRawTimeline(
            TachographFileSession session,
            DriverExtractionSession driverSession
    ) {
        String sourceKind = session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT";
        List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = rawVehicleUsageIntervals(
                session,
                driverSession,
                sourceKind
        );
        List<ResolvedActivityInterval> activityIntervals = rawActivityIntervals(driverSession, sourceKind);
        List<ExtractedSupportEvent> supportEvents = sortedSupportEvents(driverSession.supportEvents());
        return new ResolvedDriverTimeline(
                sourceKind,
                minTimestamp(vehicleUsageIntervals, activityIntervals, supportEvents),
                maxTimestamp(vehicleUsageIntervals, activityIntervals, supportEvents),
                vehicleUsageIntervals,
                activityIntervals,
                supportEvents,
                mergeWarnings(session.warnings(), driverSession.warnings())
        );
    }

    private List<ResolvedVehicleUsageInterval> rawVehicleUsageIntervals(
            TachographFileSession session,
            DriverExtractionSession driverSession,
            String sourceKind
    ) {
        if (driverSession.cardVehicleUsageIntervals() == null || driverSession.cardVehicleUsageIntervals().isEmpty()) {
            return List.of();
        }
        return driverSession.cardVehicleUsageIntervals().stream()
                .filter(interval -> interval.from() != null && (interval.to() == null || interval.to().isAfter(interval.from())))
                .map(interval -> ResolvedVehicleUsageInterval.resolved(
                        session.sessionId(),
                        driverSession.driverKey(),
                        interval.intervalId(),
                        interval.from(),
                        interval.to(),
                        interval.odometerBeginKm(),
                        interval.odometerEndKm(),
                        interval.registrationKey(),
                        interval.vehicleKey(),
                        sourceKind,
                        List.of(interval.intervalId())
                ))
                .sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
                        .thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder()))
                        .thenComparing(ResolvedVehicleUsageInterval::intervalId, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    private List<ResolvedActivityInterval> rawActivityIntervals(
            DriverExtractionSession driverSession,
            String sourceKind
    ) {
        if (driverSession.cardActivityIntervals() == null || driverSession.cardActivityIntervals().isEmpty()) {
            return List.of();
        }
        return driverSession.cardActivityIntervals().stream()
                .filter(interval -> interval.from() != null && interval.to() != null && interval.to().isAfter(interval.from()))
                .map(interval -> ResolvedActivityInterval.raw(
                        interval.intervalId(),
                        interval.from(),
                        interval.to(),
                        normalizeActivity(interval.activityType()),
                        interval.slot(),
                        interval.cardStatus(),
                        interval.drivingStatus(),
                        interval.registrationKey(),
                        interval.vehicleKey(),
                        sourceKind,
                        List.of(interval.intervalId())
                ))
                .sorted(Comparator.comparing(ResolvedActivityInterval::from)
                        .thenComparing(ResolvedActivityInterval::to)
                        .thenComparing(ResolvedActivityInterval::intervalId, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    private List<ExtractedSupportEvent> sortedSupportEvents(List<ExtractedSupportEvent> supportEvents) {
        if (supportEvents == null || supportEvents.isEmpty()) {
            return List.of();
        }
        return supportEvents.stream()
                .sorted(Comparator.comparing(ExtractedSupportEvent::occurredAt)
                        .thenComparing(ExtractedSupportEvent::eventDomain, Comparator.nullsLast(String::compareTo))
                        .thenComparing(ExtractedSupportEvent::eventId, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    private String normalizeActivity(String activityType) {
        if (activityType == null || activityType.isBlank()) {
            return "UNKNOWN";
        }
        if ("UNKNOWN_ACTIVITY".equals(activityType)) {
            return "UNKNOWN";
        }
        return activityType;
    }

    private OffsetDateTime minTimestamp(
            List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
            List<ResolvedActivityInterval> activityIntervals,
            List<ExtractedSupportEvent> supportEvents
    ) {
        OffsetDateTime min = null;
        for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
            min = min(min, interval.from());
        }
        for (ResolvedActivityInterval interval : activityIntervals) {
            min = min(min, interval.from());
        }
        for (ExtractedSupportEvent supportEvent : supportEvents) {
            min = min(min, supportEvent.occurredAt());
        }
        return min;
    }

    private OffsetDateTime maxTimestamp(
            List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
            List<ResolvedActivityInterval> activityIntervals,
            List<ExtractedSupportEvent> supportEvents
    ) {
        OffsetDateTime max = null;
        for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
            max = max(max, interval.to());
        }
        for (ResolvedActivityInterval interval : activityIntervals) {
            max = max(max, interval.to());
        }
        for (ExtractedSupportEvent supportEvent : supportEvents) {
            max = max(max, supportEvent.occurredAt());
        }
        return max;
    }

    private List<ExtractionWarning> mergeWarnings(List<ExtractionWarning> sessionWarnings, List<ExtractionWarning> driverWarnings) {
        LinkedHashSet<ExtractionWarning> merged = new LinkedHashSet<>();
        if (sessionWarnings != null) {
            merged.addAll(sessionWarnings);
        }
        if (driverWarnings != null) {
            merged.addAll(driverWarnings);
        }
        return List.copyOf(merged);
    }

    private OffsetDateTime min(OffsetDateTime left, OffsetDateTime right) {
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        return left.isBefore(right) ? left : right;
    }

    private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) {
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        return left.isAfter(right) ? left : right;
    }
}
