package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

@Component
public class VehicleUnitXmlExtractionService {

    private final DriverKeyFactory driverKeyFactory;
    private final VehicleKeyFactory vehicleKeyFactory;
    private final XmlExpressionEvaluator xml = new XmlExpressionEvaluator();

    public VehicleUnitXmlExtractionService(DriverKeyFactory driverKeyFactory, VehicleKeyFactory vehicleKeyFactory) {
        this.driverKeyFactory = driverKeyFactory;
        this.vehicleKeyFactory = vehicleKeyFactory;
    }

    public TachographFileSession extract(
            TachographXmlParser.ParsedTachographXml parsedXml,
            TachographFileSessionMetadata metadata,
            Instant createdAt,
            Instant expiresAt
    ) {
        Document document = parsedXml.document();
        List<ExtractionWarning> sessionWarnings = new ArrayList<>();

        VehicleContext vehicleContext = extractVehicleContext(document, sessionWarnings);
        Map<String, DriverExtractionBuilder> driversByKey = new LinkedHashMap<>();
        List<VuCardIwInterval> vuCardIwIntervals = new ArrayList<>();

        NodeList records = nodes(document, "/VehicleUnit/Activities/vuCardIWData/vuCardIWRecords");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuCardIWData/vuCardIWRecords[" + (i + 1) + "]";
            String driverIdentification = text(record, "fullCardNumber/cardNumber/driverIdentification");
            if (driverIdentification == null) {
                continue;
            }
            String cardNation = text(record, "fullCardNumber/cardIssuingMemberState");
            String fullCardNumber = joinCardNumber(record, "fullCardNumber/cardNumber");
            String cardNumber = driverKeyFactory.canonicalCardNumber(fullCardNumber);
            if (cardNumber == null) {
                sessionWarnings.add(new ExtractionWarning(
                        "MISSING_VU_DRIVER_CARD",
                        "Vehicle-unit insertion/withdrawal record is missing a usable driver card number.",
                        path
                ));
                continue;
            }
            String driverKey = driverKeyFactory.createDriverKey(cardNation, cardNumber);
            DriverExtractionBuilder builder = driversByKey.computeIfAbsent(
                    driverKey,
                    ignored -> new DriverExtractionBuilder(driverKey, driverKeyFactory.createSourceDriverId(driverKey))
            );
            builder.mergeDriver(
                    text(record, "cardHolderName/holderSurname/name"),
                    text(record, "cardHolderName/holderFirstNames/name")
            );
            builder.mergeDriverCard(
                    driverKeyFactory.createSourceDriverCardId(driverKey),
                    cardNation,
                    cardNumber,
                    fullCardNumber,
                    null,
                    null,
                    null,
                    offsetDateTime(text(record, "cardExpiryDate"))
            );
            builder.addVehicleContext(vehicleContext.registration(), vehicleContext.vehicle());

            OffsetDateTime from = offsetDateTime(text(record, "cardInsertionTime"));
            if (from == null) {
                sessionWarnings.add(new ExtractionWarning(
                        "MISSING_VU_CARD_INSERTION",
                        "Vehicle-unit insertion/withdrawal record is missing cardInsertionTime.",
                        path
                ));
                continue;
            }
            OffsetDateTime to = offsetDateTime(text(record, "cardWithdrawalTime"));
            if (to != null && to.isBefore(from)) {
                sessionWarnings.add(new ExtractionWarning(
                        "INVALID_VU_CARD_INTERVAL",
                        "Vehicle-unit insertion/withdrawal record has an invalid interval range.",
                        path
                ));
                continue;
            }

            builder.vehicleUsageIntervals.add(new ExtractedCardVehicleUsageInterval(
                    "VUIW-" + (i + 1),
                    from,
                    to,
                    longValue(text(record, "vehicleOdometerValueAtInsertion")),
                    longValue(text(record, "vehicleOdometerValueAtWithdrawal")),
                    vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                    vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                    path
            ));
            vuCardIwIntervals.add(new VuCardIwInterval(
                    driverKey,
                    normalizeToken(text(record, "cardSlotNumber")),
                    from,
                    to,
                    path
            ));
        }

        if (driversByKey.isEmpty()) {
            sessionWarnings.add(new ExtractionWarning(
                    "NO_DRIVER_CARD_IW_DATA",
                    "Vehicle-unit XML did not contain driver-card insertion/withdrawal records to build driver sessions.",
                    "/VehicleUnit/Activities"
            ));
        }

        List<ExtractedCardActivityInterval> vuActivityIntervals = extractActivityIntervals(document, vehicleContext, sessionWarnings);
        assignActivityCoverage(vuActivityIntervals, vuCardIwIntervals, vehicleContext, driversByKey, sessionWarnings);
        extractSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, sessionWarnings);

        List<ExtractionWarning> allWarnings = new ArrayList<>(sessionWarnings);
        Map<String, DriverExtractionSession> driverSessions = new LinkedHashMap<>();
        int activityCount = 0;
        int vehicleUsageCount = 0;
        for (DriverExtractionBuilder builder : driversByKey.values()) {
            builder.vehicleUsageIntervals.sort(Comparator.comparing(ExtractedCardVehicleUsageInterval::from));
            allWarnings.addAll(builder.warnings);
            activityCount += builder.cardActivityIntervals.size();
            vehicleUsageCount += builder.vehicleUsageIntervals.size();
            driverSessions.put(builder.driverKey, builder.build());
        }

        int registrationCount = vehicleContext.registration() == null ? 0 : 1;
        int vehicleCount = vehicleContext.vehicle() == null ? 0 : 1;
        ExtractionStats stats = new ExtractionStats(
                driverSessions.size(),
                activityCount,
                vehicleUsageCount,
                registrationCount,
                vehicleCount,
                allWarnings.size()
        );

        return new TachographFileSession(
                UUID.randomUUID(),
                metadata,
                Map.copyOf(driverSessions),
                stats,
                List.copyOf(allWarnings),
                createdAt,
                expiresAt
        );
    }

    private VehicleContext extractVehicleContext(Document document, List<ExtractionWarning> warnings) {
        Element overview = firstElement(document, "/VehicleUnit/Overview[1]");
        if (overview == null) {
            warnings.add(new ExtractionWarning(
                    "MISSING_VU_OVERVIEW",
                    "Vehicle-unit XML does not contain an Overview block.",
                    "/VehicleUnit"
            ));
            return new VehicleContext(null, null, null, null);
        }

        String registrationNation = text(overview, "vehicleRegistrationIdentification/vehicleRegistrationNation");
        String registrationNumber = text(overview, "vehicleRegistrationIdentification/vehicleRegistrationNumber/vehicleRegNumber");
        ExtractedVehicleRegistration registration = null;
        String registrationKey = registrationNation == null && registrationNumber == null
                ? null
                : vehicleKeyFactory.createRegistrationKey(registrationNation, registrationNumber);
        if (registrationKey != null) {
            registration = new ExtractedVehicleRegistration(
                    registrationKey,
                    vehicleKeyFactory.createSourceVehicleRegistrationId(registrationKey),
                    registrationNation,
                    registrationNumber
            );
        }

        String vin = text(overview, "vehicleIdentificationNumber");
        ExtractedVehicle vehicle = null;
        String vehicleKey = vehicleKeyFactory.createVehicleKey(vin);
        if (vehicleKey != null) {
            vehicle = new ExtractedVehicle(
                    vehicleKey,
                    vehicleKeyFactory.createSourceVehicleId(vehicleKey),
                    vin
            );
        }

        OffsetDateTime defaultEnd = offsetDateTime(text(overview, "vuDownloadablePeriod/maxDownloadableTime"));
        OffsetDateTime defaultStart = offsetDateTime(text(overview, "vuDownloadablePeriod/minDownloadableTime"));
        return new VehicleContext(registration, vehicle, defaultStart, defaultEnd);
    }

    private List<ExtractedCardActivityInterval> extractActivityIntervals(
            Document document,
            VehicleContext vehicleContext,
            List<ExtractionWarning> warnings
    ) {
        NodeList dayRecords = nodes(document, "/VehicleUnit/Activities/vuActivityDailyData");
        if (dayRecords.getLength() == 0) {
            return List.of();
        }

        LocalDate startDate = vehicleContext.defaultActivityStartDate();
        if (startDate == null) {
            warnings.add(new ExtractionWarning(
                    "VU_ACTIVITY_DATE_INFERENCE_FAILED",
                    "Vehicle-unit activity daily data is present but no base date could be inferred from the VU downloadable period.",
                    "/VehicleUnit/Activities/vuActivityDailyData"
            ));
            return List.of();
        }

        LocalDate maxDate = vehicleContext.defaultActivityEndDate();
        if (maxDate != null) {
            LocalDate inferredEndDate = startDate.plusDays(dayRecords.getLength() - 1L);
            if (inferredEndDate.isAfter(maxDate)) {
                warnings.add(new ExtractionWarning(
                        "VU_ACTIVITY_DATE_INFERENCE_RANGE",
                        "Vehicle-unit activity daily records exceed the VU downloadable-period range when mapped by sequence order.",
                        "/VehicleUnit/Activities/vuActivityDailyData"
                ));
            }
        }

        List<ExtractedCardActivityInterval> intervals = new ArrayList<>();
        int intervalNo = 0;
        for (int dayIndex = 0; dayIndex < dayRecords.getLength(); dayIndex++) {
            Element dayRecord = (Element) dayRecords.item(dayIndex);
            LocalDate date = startDate.plusDays(dayIndex);
            String dayPath = "/VehicleUnit/Activities[" + (dayIndex + 1) + "]/vuActivityDailyData";
            NodeList changes = nodes(dayRecord, "activityChangeInfos");
            List<ActivityChange> parsedChanges = new ArrayList<>();
            for (int changeIndex = 0; changeIndex < changes.getLength(); changeIndex++) {
                Element change = (Element) changes.item(changeIndex);
                OffsetDateTime from = combine(date, text(change, "timeOfChange"));
                if (from == null) {
                    warnings.add(new ExtractionWarning(
                            "INVALID_VU_ACTIVITY_CHANGE_TIME",
                            "Vehicle-unit activity change has invalid timeOfChange.",
                            dayPath + "/activityChangeInfos[" + (changeIndex + 1) + "]"
                    ));
                    continue;
                }
                parsedChanges.add(new ActivityChange(
                        from,
                        normalizeActivity(text(change, "activity")),
                        normalizeToken(text(change, "slot")),
                        normalizeToken(text(change, "cardStatus")),
                        normalizeToken(text(change, "drivingStatus")),
                        dayPath + "/activityChangeInfos[" + (changeIndex + 1) + "]"
                ));
            }
            parsedChanges.sort(Comparator.comparing(ActivityChange::from));
            for (int i = 0; i + 1 < parsedChanges.size(); i++) {
                ActivityChange current = parsedChanges.get(i);
                OffsetDateTime to = parsedChanges.get(i + 1).from();
                if (!current.from().isBefore(to)) {
                    continue;
                }
                intervalNo++;
                intervals.add(new ExtractedCardActivityInterval(
                        "VUACT-" + intervalNo,
                        current.from(),
                        to,
                        current.activityType(),
                        current.slot(),
                        current.cardStatus(),
                        current.drivingStatus(),
                        null,
                        null,
                        current.rawRecordPath()
                ));
            }
        }

        intervals.sort(Comparator.comparing(ExtractedCardActivityInterval::from));
        return intervals;
    }

    private void assignActivityCoverage(
            List<ExtractedCardActivityInterval> vuActivityIntervals,
            List<VuCardIwInterval> vuCardIwIntervals,
            VehicleContext vehicleContext,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        for (ExtractedCardActivityInterval interval : vuActivityIntervals) {
            List<ActivitySegment> segments = splitByDriverCoverage(interval, vuCardIwIntervals);
            if (segments.isEmpty()) {
                warnings.add(new ExtractionWarning(
                        "VU_ACTIVITY_UNASSIGNED",
                        "Vehicle-unit activity interval could not be assigned to a driver-card insertion/withdrawal interval.",
                        interval.rawRecordPath()
                ));
                continue;
            }
            if (isPartiallyCovered(interval, segments)) {
                warnings.add(new ExtractionWarning(
                        "VU_ACTIVITY_UNASSIGNED",
                        "Vehicle-unit activity interval could only be partially assigned to driver-card insertion/withdrawal intervals.",
                        interval.rawRecordPath()
                ));
            }

            for (int i = 0; i < segments.size(); i++) {
                ActivitySegment segment = segments.get(i);
                DriverExtractionBuilder builder = driversByKey.get(segment.driverKey());
                if (builder == null) {
                    warnings.add(new ExtractionWarning(
                            "VU_ACTIVITY_DRIVER_MISSING",
                            "Vehicle-unit activity interval matched a driver key without an initialized driver session.",
                            interval.rawRecordPath()
                    ));
                    continue;
                }
                String intervalId = segments.size() == 1 ? interval.intervalId() : interval.intervalId() + "-" + (i + 1);
                builder.cardActivityIntervals.add(new ExtractedCardActivityInterval(
                        intervalId,
                        segment.from(),
                        segment.to(),
                        interval.activityType(),
                        interval.slot(),
                        interval.cardStatus(),
                        interval.drivingStatus(),
                        vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                        vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                        interval.rawRecordPath()
                ));
            }
        }
    }

    private void extractSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        if (!hasKnownRegistration(vehicleContext.registration())) {
            warnings.add(new ExtractionWarning(
                    "VU_SUPPORT_EVENTS_UNKNOWN_VRN",
                    "Vehicle-unit support events were ignored because vehicle registration is unknown.",
                    "/VehicleUnit/Activities"
            ));
            return;
        }
        extractVuPlaceSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
        extractVuGnssSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
        extractVuSpecificConditionSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
        extractVuBorderCrossingSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
        extractVuLoadUnloadSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
        extractVuSpeedingSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
    }

    private void extractVuPlaceSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/Activities/vuPlaceDailyWorkPeriodData/vuPlaceDailyWorkPeriodRecords");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuPlaceDailyWorkPeriodData/vuPlaceDailyWorkPeriodRecords[" + (i + 1) + "]";
            OffsetDateTime occurredAt = offsetDateTime(text(record, "placeRecord/entryTime"));
            if (occurredAt == null) {
                warnings.add(new ExtractionWarning("VU_PLACE_MISSING_TIME", "Vehicle-unit place record is missing entryTime.", path));
                continue;
            }

            String entryType = text(record, "placeRecord/entryTypeDailyWorkPeriod");
            List<DriverAssignment> assignments = resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "fullCardNumber"),
                    null,
                    vuCardIwIntervals
            );
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_PLACE_UNASSIGNED", "Vehicle-unit place record could not be assigned to a driver session.", path));
                continue;
            }
            if (assignments.size() > 1) {
                warnings.add(new ExtractionWarning("VU_PLACE_AMBIGUOUS", "Vehicle-unit place record matched multiple active driver sessions.", path));
            }

            BigDecimal latitude = geoCoordinate(record, "placeRecord/entryGnssPlaceRecord/geoCoordinates/latitude", true);
            BigDecimal longitude = geoCoordinate(record, "placeRecord/entryGnssPlaceRecord/geoCoordinates/longitude", false);
            String authenticationStatus = normalizeToken(text(record, "placeRecord/entryGnssPlaceRecord/authenticationStatus"));
            for (DriverAssignment assignment : assignments) {
                addSupportEvent(
                        driversByKey,
                        assignment,
                        new ExtractedSupportEvent(
                                "VUPLACE-" + (i + 1) + "-" + assignment.driverKey(),
                                assignment.driverKey(),
                                occurredAt,
                                "PLACE",
                                mapPlaceEntryType(entryType),
                                null,
                                assignment.slot(),
                                vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                text(record, "placeRecord/dailyWorkPeriodCountry"),
                                text(record, "placeRecord/dailyWorkPeriodRegion"),
                                null,
                                null,
                                null,
                                latitude,
                                longitude,
                                authenticationStatus,
                                longValue(text(record, "placeRecord/vehicleOdometerValue")),
                                normalizeToken(entryType),
                                null,
                                null,
                                path
                        ),
                        warnings
                );
            }
        }
    }

    private void extractVuGnssSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/Activities/vuGnssADData/vuGnssADRecord");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuGnssADData/vuGnssADRecord[" + (i + 1) + "]";
            OffsetDateTime occurredAt = offsetDateTime(text(record, "timeStamp"));
            if (occurredAt == null) {
                warnings.add(new ExtractionWarning("VU_GNSS_MISSING_TIME", "Vehicle-unit GNSS record is missing timeStamp.", path));
                continue;
            }

            List<DriverAssignment> assignments = new ArrayList<>();
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberDriverSlot"),
                    "DRIVER",
                    vuCardIwIntervals
            ));
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
                    "CO_DRIVER",
                    vuCardIwIntervals
            ));
            assignments = distinctAssignments(assignments);
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_GNSS_UNASSIGNED", "Vehicle-unit GNSS record could not be assigned to a driver session.", path));
                continue;
            }

            BigDecimal latitude = geoCoordinate(record, "gnssPlaceRecord/geoCoordinates/latitude", true);
            BigDecimal longitude = geoCoordinate(record, "gnssPlaceRecord/geoCoordinates/longitude", false);
            String authenticationStatus = normalizeToken(text(record, "gnssPlaceRecord/authenticationStatus"));
            for (DriverAssignment assignment : assignments) {
                addSupportEvent(
                        driversByKey,
                        assignment,
                        new ExtractedSupportEvent(
                                "VUGNSS-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
                                assignment.driverKey(),
                                occurredAt,
                                "POSITION",
                                "POSITION_RECORDED",
                                "SNAPSHOT",
                                assignment.slot(),
                                vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                null,
                                null,
                                null,
                                null,
                                null,
                                latitude,
                                longitude,
                                authenticationStatus,
                                longValue(text(record, "vehicleOdometerValue")),
                                null,
                                null,
                                null,
                                path
                        ),
                        warnings
                );
            }
        }
    }

    private void extractVuSpecificConditionSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/Activities/vuSpecificConditionData/specificConditionRecords");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuSpecificConditionData/specificConditionRecords[" + (i + 1) + "]";
            OffsetDateTime occurredAt = offsetDateTime(text(record, "entryTime"));
            if (occurredAt == null) {
                warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_MISSING_TIME", "Vehicle-unit specific-condition record is missing entryTime.", path));
                continue;
            }

            List<DriverAssignment> assignments = distinctAssignments(resolveDriverAssignments(
                    occurredAt,
                    null,
                    null,
                    vuCardIwIntervals
            ));
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_UNASSIGNED", "Vehicle-unit specific-condition record could not be assigned to an active driver session.", path));
                continue;
            }
            if (assignments.size() > 1) {
                warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_AMBIGUOUS", "Vehicle-unit specific-condition record matched multiple active driver sessions.", path));
            }

            String conditionCode = normalizeToken(text(record, "specificConditionType"));
            String[] specificCondition = mapSpecificCondition(conditionCode);
            for (DriverAssignment assignment : assignments) {
                addSupportEvent(
                        driversByKey,
                        assignment,
                        new ExtractedSupportEvent(
                                "VUSC-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
                                assignment.driverKey(),
                                occurredAt,
                                "SPECIFIC_CONDITION",
                                specificCondition[0],
                                specificCondition[1],
                                assignment.slot(),
                                vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                null,
                                null,
                                null,
                                null,
                                null,
                                null,
                                null,
                                null,
                                null,
                                conditionCode,
                                null,
                                null,
                                path
                        ),
                        warnings
                );
            }
        }
    }

    private void extractVuBorderCrossingSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/Activities/vuBorderCrossingData/vuBorderCrossingRecord");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuBorderCrossingData/vuBorderCrossingRecord[" + (i + 1) + "]";
            OffsetDateTime occurredAt = offsetDateTime(text(record, "gnssPlaceAuthRecord/timeStamp"));
            if (occurredAt == null) {
                warnings.add(new ExtractionWarning("VU_BORDER_CROSSING_MISSING_TIME", "Vehicle-unit border-crossing record is missing gnssPlaceAuthRecord/timeStamp.", path));
                continue;
            }

            List<DriverAssignment> assignments = new ArrayList<>();
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberDriverSlot"),
                    "DRIVER",
                    vuCardIwIntervals
            ));
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
                    "CO_DRIVER",
                    vuCardIwIntervals
            ));
            assignments = distinctAssignments(assignments);
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_BORDER_CROSSING_UNASSIGNED", "Vehicle-unit border-crossing record could not be assigned to an active driver session.", path));
                continue;
            }

            BigDecimal latitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/latitude", true);
            BigDecimal longitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/longitude", false);
            String authenticationStatus = normalizeToken(text(record, "gnssPlaceAuthRecord/authenticationStatus"));
            for (DriverAssignment assignment : assignments) {
                addSupportEvent(
                        driversByKey,
                        assignment,
                        new ExtractedSupportEvent(
                                "VUBORDER-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
                                assignment.driverKey(),
                                occurredAt,
                                "BORDER_CROSSING",
                                "BORDER_OUTBOUND",
                                "OUTBOUND",
                                assignment.slot(),
                                vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                null,
                                null,
                                text(record, "countryLeft"),
                                text(record, "countryEntered"),
                                null,
                                latitude,
                                longitude,
                                authenticationStatus,
                                longValue(text(record, "vehicleOdometerValue")),
                                null,
                                null,
                                null,
                                path
                        ),
                        warnings
                );
            }
        }
    }

    private void extractVuLoadUnloadSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/Activities/vuLoadUnloadData/vuLoadUnloadRecord");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/Activities/vuLoadUnloadData/vuLoadUnloadRecord[" + (i + 1) + "]";
            OffsetDateTime occurredAt = offsetDateTime(text(record, "timeStamp"));
            if (occurredAt == null) {
                warnings.add(new ExtractionWarning("VU_LOAD_UNLOAD_MISSING_TIME", "Vehicle-unit load/unload record is missing timeStamp.", path));
                continue;
            }

            List<DriverAssignment> assignments = new ArrayList<>();
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberDriverSlot"),
                    "DRIVER",
                    vuCardIwIntervals
            ));
            assignments.addAll(resolveDriverAssignments(
                    occurredAt,
                    driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
                    "CO_DRIVER",
                    vuCardIwIntervals
            ));
            assignments = distinctAssignments(assignments);
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_LOAD_UNLOAD_UNASSIGNED", "Vehicle-unit load/unload record could not be assigned to an active driver session.", path));
                continue;
            }

            BigDecimal latitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/latitude", true);
            BigDecimal longitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/longitude", false);
            String authenticationStatus = normalizeToken(text(record, "gnssPlaceAuthRecord/authenticationStatus"));
            String operation = mapOperation(text(record, "operationType"));
            for (DriverAssignment assignment : assignments) {
                addSupportEvent(
                        driversByKey,
                        assignment,
                        new ExtractedSupportEvent(
                                "VULOAD-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
                                assignment.driverKey(),
                                occurredAt,
                                "LOAD_UNLOAD",
                                operation,
                                "SNAPSHOT",
                                assignment.slot(),
                                vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                null,
                                null,
                                null,
                                null,
                                operation,
                                latitude,
                                longitude,
                                authenticationStatus,
                                longValue(text(record, "vehicleOdometerValue")),
                                normalizeToken(text(record, "operationType")),
                                null,
                                null,
                                path
                        ),
                        warnings
                );
            }
        }
    }

    private void extractVuSpeedingSupportEvents(
            Document document,
            VehicleContext vehicleContext,
            List<VuCardIwInterval> vuCardIwIntervals,
            Map<String, DriverExtractionBuilder> driversByKey,
            List<ExtractionWarning> warnings
    ) {
        NodeList records = nodes(document, "/VehicleUnit/EventsFaults/vuOverSpeedingEventData/vuOverSpeedingEventRecords");
        for (int i = 0; i < records.getLength(); i++) {
            Element record = (Element) records.item(i);
            String path = "/VehicleUnit/EventsFaults/vuOverSpeedingEventData/vuOverSpeedingEventRecords[" + (i + 1) + "]";
            OffsetDateTime beginAt = offsetDateTime(text(record, "eventBeginTime"));
            OffsetDateTime endAt = offsetDateTime(text(record, "eventEndTime"));
            if (beginAt == null && endAt == null) {
                warnings.add(new ExtractionWarning("VU_SPEEDING_MISSING_TIME", "Vehicle-unit speeding record is missing both eventBeginTime and eventEndTime.", path));
                continue;
            }

            List<DriverAssignment> assignments = distinctAssignments(resolveDriverAssignments(
                    beginAt != null ? beginAt : endAt,
                    driverKeyFromCardNode(record, "cardNumberDriverSlotBegin"),
                    "DRIVER",
                    vuCardIwIntervals
            ));
            if (assignments.isEmpty()) {
                warnings.add(new ExtractionWarning("VU_SPEEDING_UNASSIGNED", "Vehicle-unit speeding record could not be assigned to an active driver session.", path));
                continue;
            }

            BigDecimal avgSpeedKmh = decimalValue(text(record, "averageSpeedValue"));
            BigDecimal maxSpeedKmh = decimalValue(text(record, "maxSpeedValue"));
            for (DriverAssignment assignment : assignments) {
                if (beginAt != null) {
                    addSupportEvent(
                            driversByKey,
                            assignment,
                            new ExtractedSupportEvent(
                                    "VUSPEED-" + (i + 1) + "-" + assignment.driverKey() + "-BEGIN",
                                    assignment.driverKey(),
                                    beginAt,
                                    "SPEEDING",
                                    "SPEEDING",
                                    "BEGIN",
                                    assignment.slot(),
                                    vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                    vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    normalizeToken(text(record, "eventType")),
                                    avgSpeedKmh,
                                    maxSpeedKmh,
                                    path
                            ),
                            warnings
                    );
                }
                if (endAt != null) {
                    addSupportEvent(
                            driversByKey,
                            assignment,
                            new ExtractedSupportEvent(
                                    "VUSPEED-" + (i + 1) + "-" + assignment.driverKey() + "-END",
                                    assignment.driverKey(),
                                    endAt,
                                    "SPEEDING",
                                    "SPEEDING",
                                    "END",
                                    assignment.slot(),
                                    vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
                                    vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    null,
                                    normalizeToken(text(record, "eventType")),
                                    avgSpeedKmh,
                                    maxSpeedKmh,
                                    path
                            ),
                            warnings
                    );
                }
            }
        }
    }

    private boolean isPartiallyCovered(ExtractedCardActivityInterval interval, List<ActivitySegment> segments) {
        if (segments.isEmpty()) {
            return true;
        }
        if (!segments.get(0).from().equals(interval.from())) {
            return true;
        }
        if (!segments.get(segments.size() - 1).to().equals(interval.to())) {
            return true;
        }
        for (int i = 1; i < segments.size(); i++) {
            if (!segments.get(i - 1).to().equals(segments.get(i).from())) {
                return true;
            }
        }
        return false;
    }

    private List<ActivitySegment> splitByDriverCoverage(
            ExtractedCardActivityInterval interval,
            List<VuCardIwInterval> vuCardIwIntervals
    ) {
        TreeSet<OffsetDateTime> cutPoints = new TreeSet<>();
        cutPoints.add(interval.from());
        cutPoints.add(interval.to());

        List<VuCardIwInterval> matchingIntervals = vuCardIwIntervals.stream()
                .filter(iw -> iw.slot() == null || interval.slot() == null || iw.slot().equals(interval.slot()))
                .filter(iw -> iw.overlaps(interval.from(), interval.to()))
                .toList();

        for (VuCardIwInterval iw : matchingIntervals) {
            if (iw.from().isAfter(interval.from()) && iw.from().isBefore(interval.to())) {
                cutPoints.add(iw.from());
            }
            OffsetDateTime iwEndExclusive = iw.endExclusive();
            if (iwEndExclusive.isAfter(interval.from()) && iwEndExclusive.isBefore(interval.to())) {
                cutPoints.add(iwEndExclusive);
            }
        }

        List<OffsetDateTime> orderedCutPoints = List.copyOf(cutPoints);
        List<ActivitySegment> segments = new ArrayList<>();
        for (int i = 0; i < orderedCutPoints.size() - 1; i++) {
            OffsetDateTime segmentFrom = orderedCutPoints.get(i);
            OffsetDateTime segmentTo = orderedCutPoints.get(i + 1);
            if (!segmentFrom.isBefore(segmentTo)) {
                continue;
            }
            VuCardIwInterval covering = matchingIntervals.stream()
                    .filter(iw -> iw.covers(segmentFrom))
                    .findFirst()
                    .orElse(null);
            if (covering != null) {
                segments.add(new ActivitySegment(covering.driverKey(), segmentFrom, segmentTo));
            }
        }
        return segments;
    }

    private void addSupportEvent(
            Map<String, DriverExtractionBuilder> driversByKey,
            DriverAssignment assignment,
            ExtractedSupportEvent supportEvent,
            List<ExtractionWarning> warnings
    ) {
        DriverExtractionBuilder builder = driversByKey.get(assignment.driverKey());
        if (builder == null) {
            warnings.add(new ExtractionWarning(
                    "VU_SUPPORT_EVENT_DRIVER_MISSING",
                    "Support event matched a driver key without an initialized driver session.",
                    supportEvent.rawRecordPath()
            ));
            return;
        }
        builder.supportEvents.add(supportEvent);
    }

    private boolean hasKnownRegistration(ExtractedVehicleRegistration registration) {
        return registration != null && hasKnownRegistrationKey(registration.registrationKey());
    }

    private boolean hasKnownRegistrationKey(String registrationKey) {
        return registrationKey != null
                && !registrationKey.isBlank()
                && !registrationKey.startsWith("UNKNOWN:")
                && !registrationKey.endsWith(":UNKNOWN");
    }

    private List<DriverAssignment> resolveDriverAssignments(
            OffsetDateTime occurredAt,
            String explicitDriverKey,
            String explicitSlot,
            List<VuCardIwInterval> vuCardIwIntervals
    ) {
        if (explicitDriverKey != null) {
            return List.of(new DriverAssignment(explicitDriverKey, explicitSlot));
        }
        return vuCardIwIntervals.stream()
                .filter(iw -> explicitSlot == null || explicitSlot.equals(iw.slot()))
                .filter(iw -> iw.covers(occurredAt))
                .map(iw -> new DriverAssignment(iw.driverKey(), iw.slot()))
                .toList();
    }

    private List<DriverAssignment> distinctAssignments(List<DriverAssignment> assignments) {
        Map<String, DriverAssignment> unique = new LinkedHashMap<>();
        for (DriverAssignment assignment : assignments) {
            unique.putIfAbsent(assignment.driverKey() + "|" + assignment.slot(), assignment);
        }
        return List.copyOf(unique.values());
    }

    private String driverKeyFromCardNode(Element node, String basePath) {
        String cardNation = text(node, basePath + "/cardIssuingMemberState");
        String cardNumber = driverKeyFactory.canonicalCardNumber(joinCardNumber(node, basePath + "/cardNumber"));
        return cardNumber == null ? null : driverKeyFactory.createDriverKey(cardNation, cardNumber);
    }

    private Element firstElement(Object node, String expression) {
        NodeList nodes = nodes(node, expression);
        if (nodes.getLength() == 0) {
            return null;
        }
        return (Element) nodes.item(0);
    }

    private NodeList nodes(Object node, String expression) {
        return xml.nodes(node, expression);
    }

    private String text(Object node, String expression) {
        return xml.text(node, expression);
    }

    private OffsetDateTime offsetDateTime(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return OffsetDateTime.parse(value.trim()).withOffsetSameInstant(ZoneOffset.UTC);
    }

    private Long longValue(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return Long.parseLong(value.trim());
    }

    private BigDecimal decimalValue(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return new BigDecimal(value.trim());
    }

    private BigDecimal geoCoordinate(Object node, String expression, boolean latitude) {
        String value = text(node, expression);
        if (value == null) {
            return null;
        }
        BigDecimal raw = new BigDecimal(value);
        BigDecimal abs = raw.abs();
        BigDecimal degreeThreshold = BigDecimal.valueOf(latitude ? 90D : 180D);
        if (abs.compareTo(degreeThreshold) <= 0) {
            return raw;
        }
        BigDecimal sign = raw.signum() < 0 ? BigDecimal.valueOf(-1L) : BigDecimal.ONE;
        BigDecimal absolute = raw.abs();
        BigDecimal[] degreeAndRemainder = absolute.divideAndRemainder(BigDecimal.valueOf(1000L));
        BigDecimal degrees = degreeAndRemainder[0];
        BigDecimal minutes = degreeAndRemainder[1].divide(BigDecimal.TEN);
        BigDecimal decimalDegrees = degrees.add(minutes.divide(BigDecimal.valueOf(60L), 8, java.math.RoundingMode.HALF_UP));
        return decimalDegrees.multiply(sign);
    }

    private OffsetDateTime combine(LocalDate date, String timeText) {
        return TachographTimeParser.combineUtc(date, timeText);
    }

    private String normalizeActivity(String value) {
        String normalized = normalizeToken(value);
        if (normalized == null) {
            return "UNKNOWN_ACTIVITY";
        }
        return switch (normalized) {
            case "DRIVING", "DRIVE" -> "DRIVE";
            case "WORK" -> "WORK";
            case "AVAILABILITY", "AVAILABLE" -> "AVAILABILITY";
            case "BREAK_REST", "BREAK/REST", "REST" -> "BREAK_REST";
            default -> "UNKNOWN_ACTIVITY";
        };
    }

    private String normalizeToken(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
    }

    private String mapPlaceEntryType(String entryType) {
        String normalized = normalizeToken(entryType);
        if (normalized == null) {
            return "DAILY_WORK_PERIOD_PLACE";
        }
        return switch (normalized) {
            case "0" -> "BEGIN_DAILY_WORK_PERIOD";
            case "1" -> "END_DAILY_WORK_PERIOD";
            case "2" -> "BEGIN_MANUAL_DAILY_WORK_PERIOD";
            case "3" -> "END_MANUAL_DAILY_WORK_PERIOD";
            case "4" -> "BEGIN_ASSUMED_DAILY_WORK_PERIOD";
            case "5" -> "END_ASSUMED_DAILY_WORK_PERIOD";
            case "6" -> "BEGIN_GNSS_DAILY_WORK_PERIOD";
            case "7" -> "END_GNSS_DAILY_WORK_PERIOD";
            default -> "DAILY_WORK_PERIOD_PLACE";
        };
    }

    private String[] mapSpecificCondition(String conditionCode) {
        String normalized = normalizeToken(conditionCode);
        if (normalized == null) {
            return new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
        }
        return switch (normalized) {
            case "1" -> new String[]{"OUT", "BEGIN"};
            case "2" -> new String[]{"OUT", "END"};
            case "3" -> new String[]{"FERRY_TRAIN", "BEGIN"};
            case "4" -> new String[]{"FERRY_TRAIN", "END"};
            default -> new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
        };
    }

    private String mapOperation(String operationType) {
        String normalized = normalizeToken(operationType);
        if (normalized == null) {
            return "LOAD_UNLOAD";
        }
        return switch (normalized) {
            case "1", "LOAD" -> "LOAD";
            case "2", "UNLOAD" -> "UNLOAD";
            default -> "LOAD_UNLOAD";
        };
    }

    private String joinCardNumber(Element node, String basePath) {
        String driverIdentification = text(node, basePath + "/driverIdentification");
        if (driverIdentification == null) {
            return null;
        }
        String replacement = text(node, basePath + "/cardReplacementIndex");
        String renewal = text(node, basePath + "/cardRenewalIndex");
        StringBuilder builder = new StringBuilder(driverIdentification);
        if (replacement != null) {
            builder.append(replacement);
        }
        if (renewal != null) {
            builder.append(renewal);
        }
        return builder.toString();
    }

    private static final class DriverExtractionBuilder {
        private final String driverKey;
        private final String sourceDriverId;
        private ExtractedDriver driver;
        private ExtractedDriverCard driverCard;
        private final Map<String, ExtractedVehicleRegistration> registrationsByKey = new LinkedHashMap<>();
        private final Map<String, ExtractedVehicle> vehiclesByKey = new LinkedHashMap<>();
        private final List<ExtractedCardVehicleUsageInterval> vehicleUsageIntervals = new ArrayList<>();
        private final List<ExtractedCardActivityInterval> cardActivityIntervals = new ArrayList<>();
        private final List<ExtractedSupportEvent> supportEvents = new ArrayList<>();
        private final List<ExtractionWarning> warnings = new ArrayList<>();

        private DriverExtractionBuilder(String driverKey, String sourceDriverId) {
            this.driverKey = driverKey;
            this.sourceDriverId = sourceDriverId;
        }

        private void mergeDriver(String surname, String firstNames) {
            if (driver == null) {
                driver = new ExtractedDriver(
                        driverKey,
                        sourceDriverId,
                        surname,
                        firstNames,
                        null,
                        null,
                        null,
                        null,
                        null
                );
            }
        }

        private void mergeDriverCard(
                String sourceDriverCardId,
                String cardNation,
                String cardNumber,
                String fullCardNumber,
                String issuingAuthorityName,
                OffsetDateTime issueDate,
                OffsetDateTime validityBegin,
                OffsetDateTime expiryDate
        ) {
            if (driverCard == null) {
                driverCard = new ExtractedDriverCard(
                        sourceDriverCardId,
                        cardNation,
                        cardNumber,
                        fullCardNumber,
                        issuingAuthorityName,
                        issueDate,
                        validityBegin,
                        expiryDate
                );
            }
        }

        private void addVehicleContext(ExtractedVehicleRegistration registration, ExtractedVehicle vehicle) {
            if (registration != null) {
                registrationsByKey.putIfAbsent(registration.registrationKey(), registration);
            }
            if (vehicle != null) {
                vehiclesByKey.putIfAbsent(vehicle.vehicleKey(), vehicle);
            }
        }

        private DriverExtractionSession build() {
            return new DriverExtractionSession(
                    driverKey,
                    driver,
                    driverCard,
                    List.copyOf(registrationsByKey.values()),
                    List.copyOf(vehiclesByKey.values()),
                    List.copyOf(vehicleUsageIntervals),
                    List.copyOf(cardActivityIntervals),
                    List.copyOf(supportEvents),
                    List.copyOf(warnings)
            );
        }
    }

    private record VehicleContext(
            ExtractedVehicleRegistration registration,
            ExtractedVehicle vehicle,
            OffsetDateTime defaultActivityStart,
            OffsetDateTime defaultOpenIntervalEnd
    ) {
        private LocalDate defaultActivityStartDate() {
            return defaultActivityStart == null ? null : defaultActivityStart.withOffsetSameInstant(ZoneOffset.UTC).toLocalDate();
        }

        private LocalDate defaultActivityEndDate() {
            return defaultOpenIntervalEnd == null ? null : defaultOpenIntervalEnd.withOffsetSameInstant(ZoneOffset.UTC).toLocalDate();
        }
    }

    private record ActivityChange(
            OffsetDateTime from,
            String activityType,
            String slot,
            String cardStatus,
            String drivingStatus,
            String rawRecordPath
    ) {
    }

    private record VuCardIwInterval(
            String driverKey,
            String slot,
            OffsetDateTime from,
            OffsetDateTime to,
            String rawRecordPath
    ) {
        private OffsetDateTime endExclusive() {
            return to == null ? OffsetDateTime.MAX : to.plusSeconds(1);
        }

        private boolean covers(OffsetDateTime timestamp) {
            return !from.isAfter(timestamp) && timestamp.isBefore(endExclusive());
        }

        private boolean overlaps(OffsetDateTime rangeStart, OffsetDateTime rangeEnd) {
            return endExclusive().isAfter(rangeStart) && from.isBefore(rangeEnd);
        }
    }

    private record ActivitySegment(
            String driverKey,
            OffsetDateTime from,
            OffsetDateTime to
    ) {
    }

    private record DriverAssignment(
            String driverKey,
            String slot
    ) {
    }
}
