package de.votian.web.view;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.votian.web.model.SessionData;
import de.votian.web.service.RestClientService;
import org.springframework.core.ParameterizedTypeReference;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@Route(value = "tracking", layout = MainLayout.class)
@PageTitle("Votian - Tracking")
public class TrackingView extends VerticalLayout {

    private static final long ALL_CUSTOMERS_ID = -1L;

    private final RestClientService restClient;
    private final SessionData sessionData;

    private final DatePicker dayField = new DatePicker("Tag");
    private final Checkbox includeCompletedField = new Checkbox("Abgeschlossene anzeigen");
    private final ComboBox<CustomerOption> customerField = new ComboBox<>("Kunde");
    private final Button reloadButton = new Button("Neu laden", VaadinIcon.REFRESH.create());
    private final Button openJobButton = new Button("Auftrag oeffnen", VaadinIcon.EXTERNAL_LINK.create());

    private final Span totalJobs = new Span("-");
    private final Span assignedJobs = new Span("-");
    private final Span trackedJobs = new Span("-");
    private final Span totalCouriers = new Span("-");
    private final Span gpsCouriers = new Span("-");
    private final Span fallbackCouriers = new Span("-");

    private final Span mapInfo = new Span("-");
    private final Div mapView = new Div();

    private final Grid<TrackingJobRow> jobGrid = new Grid<>(TrackingJobRow.class, false);
    private final Grid<TrackingCourierRow> courierGrid = new Grid<>(TrackingCourierRow.class, false);

    private final Span detailJobId = new Span("-");
    private final Span detailJobStatus = new Span("-");
    private final Span detailJobCustomer = new Span("-");
    private final Span detailJobCostCenter = new Span("-");
    private final Span detailJobRoute = new Span("-");
    private final Span detailJobCourier = new Span("-");
    private final Span detailJobTimes = new Span("-");
    private final Span detailJobTracking = new Span("-");
    private final Span detailJobPrice = new Span("-");

    private final Span detailCourierSid = new Span("-");
    private final Span detailCourierCompany = new Span("-");
    private final Span detailCourierVehicle = new Span("-");
    private final Span detailCourierJobs = new Span("-");
    private final Span detailCourierMobile = new Span("-");
    private final Span detailCourierTracking = new Span("-");
    private final Span detailCourierAvailable = new Span("-");

    private final List<TrackingJobRow> jobs = new ArrayList<>();
    private final List<TrackingCourierRow> couriers = new ArrayList<>();

    private TrackingJobRow selectedJob;
    private TrackingCourierRow selectedCourier;

    public TrackingView(RestClientService restClient, SessionData sessionData) {
        this.restClient = restClient;
        this.sessionData = sessionData;

        setSizeFull();
        setPadding(true);
        setSpacing(true);

        dayField.setValue(LocalDate.now());
        customerField.setItemLabelGenerator(CustomerOption::label);
        customerField.setWidth("320px");
        customerField.setVisible(sessionData.isHeadquartersUser() && sessionData.canViewCustomers());
        customerField.addValueChangeListener(event -> loadDashboard());

        includeCompletedField.addValueChangeListener(event -> loadDashboard());

        reloadButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        reloadButton.addClickListener(event -> loadDashboard());

        openJobButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        openJobButton.addClickListener(event -> {
            if (selectedJob != null && sessionData.canViewJobs()) {
                getUI().ifPresent(ui -> ui.navigate("jobs/" + selectedJob.jobId()));
            }
        });

        configureMapView();
        configureJobGrid();
        configureCourierGrid();
        refreshActionState();
        clearDetail();

        HorizontalLayout workspace = buildWorkspace();
        add(
                new H3("Tracking"),
                buildFilterBar(),
                buildCards(),
                buildMapPanel(),
                workspace,
                buildDetailPanels()
        );
        expand(workspace);

        if (customerField.isVisible()) {
            loadCustomers();
        }
        loadDashboard();
    }

    private HorizontalLayout buildFilterBar() {
        HorizontalLayout filterBar = new HorizontalLayout(dayField, includeCompletedField, customerField, reloadButton, openJobButton);
        filterBar.setAlignItems(Alignment.BASELINE);
        filterBar.getStyle().set("flex-wrap", "wrap");
        return filterBar;
    }

    private HorizontalLayout buildCards() {
        HorizontalLayout cards = new HorizontalLayout(
                createCard("Auftraege", totalJobs),
                createCard("Mit Kurier", assignedJobs),
                createCard("Mit Tracking", trackedJobs),
                createCard("Kuriere", totalCouriers),
                createCard("GPS", gpsCouriers),
                createCard("PLZ-Fallback", fallbackCouriers)
        );
        cards.setWidthFull();
        cards.getStyle().set("flex-wrap", "wrap");
        return cards;
    }

    private Component buildMapPanel() {
        VerticalLayout panel = new VerticalLayout(new H4("Karte"), mapInfo, mapView);
        panel.setPadding(false);
        panel.setSpacing(true);
        return panel;
    }

    private HorizontalLayout buildWorkspace() {
        VerticalLayout jobPanel = new VerticalLayout(new H4("Auftraege"), jobGrid);
        jobPanel.setPadding(false);
        jobPanel.setSpacing(true);
        jobPanel.setSizeFull();
        jobPanel.expand(jobGrid);

        VerticalLayout courierPanel = new VerticalLayout(new H4("Kuriere"), courierGrid);
        courierPanel.setPadding(false);
        courierPanel.setSpacing(true);
        courierPanel.setSizeFull();
        courierPanel.expand(courierGrid);

        HorizontalLayout workspace = new HorizontalLayout(jobPanel, courierPanel);
        workspace.setSizeFull();
        workspace.expand(jobPanel, courierPanel);
        return workspace;
    }

    private HorizontalLayout buildDetailPanels() {
        VerticalLayout jobDetailPanel = new VerticalLayout(
                new H4("Auftragsdetails"),
                detailLine("Auftrag", detailJobId),
                detailLine("Status", detailJobStatus),
                detailLine("Kunde", detailJobCustomer),
                detailLine("Kostenstelle", detailJobCostCenter),
                detailLine("Route", detailJobRoute),
                detailLine("Kurier", detailJobCourier),
                detailLine("Zeiten", detailJobTimes),
                detailLine("Tracking", detailJobTracking),
                detailLine("Preis", detailJobPrice)
        );
        jobDetailPanel.setPadding(true);
        jobDetailPanel.setSpacing(true);
        jobDetailPanel.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)");

        VerticalLayout courierDetailPanel = new VerticalLayout(
                new H4("Kurierdetails"),
                detailLine("Kurier", detailCourierSid),
                detailLine("Firma", detailCourierCompany),
                detailLine("Fahrzeug", detailCourierVehicle),
                detailLine("Aktive Auftraege", detailCourierJobs),
                detailLine("Mobile/PDA", detailCourierMobile),
                detailLine("Tracking", detailCourierTracking),
                detailLine("Verfuegbar seit", detailCourierAvailable)
        );
        courierDetailPanel.setPadding(true);
        courierDetailPanel.setSpacing(true);
        courierDetailPanel.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)");

        HorizontalLayout panels = new HorizontalLayout(jobDetailPanel, courierDetailPanel);
        panels.setWidthFull();
        panels.expand(jobDetailPanel, courierDetailPanel);
        return panels;
    }

    private HorizontalLayout detailLine(String label, Span value) {
        Span title = new Span(label + ":");
        title.getStyle().set("font-weight", "600").set("min-width", "110px");
        HorizontalLayout line = new HorizontalLayout(title, value);
        line.setAlignItems(Alignment.BASELINE);
        line.setWidthFull();
        return line;
    }

    private VerticalLayout createCard(String title, Span value) {
        Span heading = new Span(title);
        heading.getStyle().set("font-size", "var(--lumo-font-size-s)");
        value.getStyle().set("font-size", "var(--lumo-font-size-l)").set("font-weight", "700");
        VerticalLayout card = new VerticalLayout(heading, value);
        card.setPadding(true);
        card.setSpacing(false);
        card.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)")
                .set("background", "var(--lumo-base-color)")
                .set("min-width", "160px");
        return card;
    }

    private void configureMapView() {
        mapView.setWidthFull();
        mapView.setHeight("360px");
        mapView.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)")
                .set("overflow", "hidden")
                .set("background", "linear-gradient(180deg, #edf6ff 0%, #f7f9fc 100%)");
        mapInfo.getStyle()
                .set("font-size", "var(--lumo-font-size-s)")
                .set("color", "var(--lumo-secondary-text-color)");
    }

    private void configureJobGrid() {
        jobGrid.addColumn(TrackingJobRow::jobId).setHeader("Auftrag").setAutoWidth(true).setFrozen(true);
        jobGrid.addColumn(TrackingJobRow::statusLabel).setHeader("Status").setAutoWidth(true);
        jobGrid.addColumn(row -> formatDateTime(row.orderTime())).setHeader("Zeit").setAutoWidth(true);
        jobGrid.addColumn(TrackingJobRow::customerName).setHeader("Kunde").setFlexGrow(1);
        jobGrid.addColumn(TrackingJobRow::costCenterName).setHeader("Kostenstelle").setFlexGrow(1);
        jobGrid.addColumn(TrackingJobRow::pickupLabel).setHeader("Start").setFlexGrow(1);
        jobGrid.addColumn(TrackingJobRow::deliveryLabel).setHeader("Ziel").setFlexGrow(1);
        jobGrid.addColumn(TrackingJobRow::courierSid).setHeader("Kurier").setAutoWidth(true);
        jobGrid.addColumn(TrackingJobRow::trackingLabel).setHeader("Tracking").setAutoWidth(true);
        jobGrid.addColumn(row -> formatAmount(row.totalPrice())).setHeader("Preis").setAutoWidth(true);
        jobGrid.setSizeFull();
        jobGrid.asSingleSelect().addValueChangeListener(event -> {
            selectedJob = event.getValue();
            if (selectedJob != null && selectedJob.courierId() != null) {
                TrackingCourierRow matchingCourier = couriers.stream()
                        .filter(courier -> selectedJob.courierId().equals(courier.courierId()))
                        .findFirst()
                        .orElse(null);
                courierGrid.asSingleSelect().setValue(matchingCourier);
                selectedCourier = matchingCourier;
            }
            if (selectedJob == null) {
                selectedCourier = courierGrid.asSingleSelect().getValue();
            }
            updateDetail();
            updateMap();
            refreshActionState();
        });
    }

    private void configureCourierGrid() {
        courierGrid.addColumn(TrackingCourierRow::courierSid).setHeader("Kurier").setAutoWidth(true).setFrozen(true);
        courierGrid.addColumn(TrackingCourierRow::companyName).setHeader("Firma").setFlexGrow(1);
        courierGrid.addColumn(TrackingCourierRow::vehicleDisplay).setHeader("Fahrzeug").setAutoWidth(true);
        courierGrid.addColumn(TrackingCourierRow::activeJobs).setHeader("Jobs").setAutoWidth(true);
        courierGrid.addColumn(TrackingCourierRow::locationZipcode).setHeader("PLZ").setAutoWidth(true);
        courierGrid.addColumn(TrackingCourierRow::trackingLabel).setHeader("Tracking").setAutoWidth(true);
        courierGrid.addColumn(row -> formatDateTime(row.gpsTime())).setHeader("Zeit").setAutoWidth(true);
        courierGrid.setSizeFull();
        courierGrid.asSingleSelect().addValueChangeListener(event -> {
            selectedCourier = event.getValue();
            updateDetail();
            updateMap();
        });
    }

    private void loadCustomers() {
        try {
            List<LinkedHashMap<String, Object>> rows = restClient.getList(
                    "/customers/hq/" + sessionData.getHeadquartersId(),
                    new ParameterizedTypeReference<>() {}
            );
            List<CustomerOption> options = new ArrayList<>();
            options.add(new CustomerOption(ALL_CUSTOMERS_ID, "Alle Kunden"));
            for (Map<String, Object> row : rows) {
                Long id = toLong(row.get("id"));
                if (id != null) {
                    options.add(new CustomerOption(id, str(row.get("companyName"))));
                }
            }
            options.sort(Comparator.comparing(CustomerOption::label, String.CASE_INSENSITIVE_ORDER));
            if (!options.isEmpty()) {
                CustomerOption allOption = options.stream()
                        .filter(option -> option.id().equals(ALL_CUSTOMERS_ID))
                        .findFirst()
                        .orElse(new CustomerOption(ALL_CUSTOMERS_ID, "Alle Kunden"));
                options.removeIf(option -> option.id().equals(ALL_CUSTOMERS_ID));
                options.add(0, allOption);
            }
            customerField.setItems(options);
            customerField.setValue(options.getFirst());
        } catch (Exception e) {
            Notification.show("Kundenfilter konnte nicht geladen werden: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadDashboard() {
        if (sessionData.getHeadquartersId() == null || dayField.getValue() == null) {
            return;
        }
        try {
            StringBuilder path = new StringBuilder("/tracking/dashboard?hqId=")
                    .append(sessionData.getHeadquartersId())
                    .append("&day=").append(dayField.getValue())
                    .append("&includeCompleted=").append(includeCompletedField.getValue());
            CustomerOption customer = customerField.getValue();
            if (customerField.isVisible() && customer != null && !customer.id().equals(ALL_CUSTOMERS_ID)) {
                path.append("&customerId=").append(customer.id());
            }

            Map<String, Object> data = restClient.getMap(path.toString());
            jobs.clear();
            jobs.addAll(parseJobs(data.get("jobs")));
            couriers.clear();
            couriers.addAll(parseCouriers(data.get("couriers")));

            totalJobs.setText(str(data.get("totalJobs"), jobs.size()));
            assignedJobs.setText(str(data.get("assignedJobs"), 0));
            trackedJobs.setText(str(data.get("trackedJobs"), 0));
            totalCouriers.setText(str(data.get("totalCouriers"), couriers.size()));
            gpsCouriers.setText(str(data.get("gpsCouriers"), 0));
            fallbackCouriers.setText(str(data.get("fallbackCouriers"), 0));

            jobGrid.setItems(jobs);
            courierGrid.setItems(couriers);

            if (selectedJob != null) {
                TrackingJobRow persistedJob = jobs.stream()
                        .filter(row -> row.jobId().equals(selectedJob.jobId()))
                        .findFirst()
                        .orElse(null);
                jobGrid.asSingleSelect().setValue(persistedJob);
                selectedJob = persistedJob;
            } else {
                jobGrid.asSingleSelect().clear();
            }

            if (selectedCourier != null) {
                TrackingCourierRow persistedCourier = couriers.stream()
                        .filter(row -> row.courierId().equals(selectedCourier.courierId()))
                        .findFirst()
                        .orElse(null);
                courierGrid.asSingleSelect().setValue(persistedCourier);
                selectedCourier = persistedCourier;
            } else {
                courierGrid.asSingleSelect().clear();
            }

            if (selectedJob == null && !jobs.isEmpty()) {
                jobGrid.asSingleSelect().setValue(jobs.getFirst());
                selectedJob = jobs.getFirst();
            }

            if (selectedCourier == null && !couriers.isEmpty()) {
                selectedCourier = couriers.getFirst();
                courierGrid.asSingleSelect().setValue(selectedCourier);
            }

            updateDetail();
            updateMap();
            refreshActionState();
        } catch (Exception e) {
            Notification.show("Tracking konnte nicht geladen werden: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private List<TrackingJobRow> parseJobs(Object rawValue) {
        if (!(rawValue instanceof List<?> rawList)) {
            return List.of();
        }
        List<TrackingJobRow> rows = new ArrayList<>();
        for (Object entry : rawList) {
            if (!(entry instanceof Map<?, ?> rawMap)) {
                continue;
            }
            rows.add(new TrackingJobRow(
                    toLong(rawMap.get("jobId")),
                    toInteger(rawMap.get("status")),
                    str(rawMap.get("statusLabel")),
                    str(rawMap.get("orderTime")),
                    str(rawMap.get("takeTime")),
                    str(rawMap.get("finishTime")),
                    toLong(rawMap.get("customerId")),
                    str(rawMap.get("customerName")),
                    str(rawMap.get("costCenterName")),
                    str(rawMap.get("commissionNo")),
                    str(rawMap.get("pickupName")),
                    str(rawMap.get("pickupZipcode")),
                    str(rawMap.get("pickupCity")),
                    toDouble(rawMap.get("pickupLatitude")),
                    toDouble(rawMap.get("pickupLongitude")),
                    str(rawMap.get("deliveryName")),
                    str(rawMap.get("deliveryZipcode")),
                    str(rawMap.get("deliveryCity")),
                    toDouble(rawMap.get("deliveryLatitude")),
                    toDouble(rawMap.get("deliveryLongitude")),
                    toLong(rawMap.get("courierId")),
                    str(rawMap.get("courierSid")),
                    str(rawMap.get("courierCompanyName")),
                    str(rawMap.get("courierVehicleSid")),
                    str(rawMap.get("vehicleTypeName")),
                    toDouble(rawMap.get("courierLatitude")),
                    toDouble(rawMap.get("courierLongitude")),
                    str(rawMap.get("courierGpsTime")),
                    str(rawMap.get("courierGpsTypeLabel")),
                    str(rawMap.get("courierLocationZipcode")),
                    bool(rawMap.get("courierTracked")),
                    bool(rawMap.get("courierGpsBased")),
                    toBigDecimal(rawMap.get("totalPrice"))
            ));
        }
        return rows;
    }

    private List<TrackingCourierRow> parseCouriers(Object rawValue) {
        if (!(rawValue instanceof List<?> rawList)) {
            return List.of();
        }
        List<TrackingCourierRow> rows = new ArrayList<>();
        for (Object entry : rawList) {
            if (!(entry instanceof Map<?, ?> rawMap)) {
                continue;
            }
            rows.add(new TrackingCourierRow(
                    toLong(rawMap.get("courierId")),
                    str(rawMap.get("courierSid")),
                    str(rawMap.get("companyName")),
                    str(rawMap.get("vehicleDisplay")),
                    toInteger(rawMap.get("activeJobs")) != null ? toInteger(rawMap.get("activeJobs")) : 0,
                    str(rawMap.get("locationZipcode")),
                    toDouble(rawMap.get("latitude")),
                    toDouble(rawMap.get("longitude")),
                    str(rawMap.get("gpsTime")),
                    str(rawMap.get("gpsTypeLabel")),
                    str(rawMap.get("mobilePda")),
                    str(rawMap.get("availableTime")),
                    bool(rawMap.get("tracked")),
                    bool(rawMap.get("gpsBased"))
            ));
        }
        return rows;
    }

    private void refreshActionState() {
        openJobButton.setEnabled(selectedJob != null && sessionData.canViewJobs());
    }

    private void updateDetail() {
        if (selectedJob == null) {
            detailJobId.setText("-");
            detailJobStatus.setText("-");
            detailJobCustomer.setText("-");
            detailJobCostCenter.setText("-");
            detailJobRoute.setText("-");
            detailJobCourier.setText("-");
            detailJobTimes.setText("-");
            detailJobTracking.setText("-");
            detailJobPrice.setText("-");
        } else {
            detailJobId.setText(str(selectedJob.jobId()));
            detailJobStatus.setText(orDash(selectedJob.statusLabel()));
            detailJobCustomer.setText(orDash(selectedJob.customerName()));
            detailJobCostCenter.setText(orDash(selectedJob.costCenterName()));
            detailJobRoute.setText(orDash(selectedJob.pickupLabel() + " -> " + selectedJob.deliveryLabel()));
            detailJobCourier.setText(orDash(selectedJob.courierSid()));
            detailJobTimes.setText(orDash(formatJobTimes(selectedJob)));
            detailJobTracking.setText(orDash(selectedJob.trackingLabel()));
            detailJobPrice.setText(orDash(formatAmount(selectedJob.totalPrice())));
        }

        if (selectedCourier == null) {
            detailCourierSid.setText("-");
            detailCourierCompany.setText("-");
            detailCourierVehicle.setText("-");
            detailCourierJobs.setText("-");
            detailCourierMobile.setText("-");
            detailCourierTracking.setText("-");
            detailCourierAvailable.setText("-");
        } else {
            detailCourierSid.setText(orDash(selectedCourier.courierSid()));
            detailCourierCompany.setText(orDash(selectedCourier.companyName()));
            detailCourierVehicle.setText(orDash(selectedCourier.vehicleDisplay()));
            detailCourierJobs.setText(String.valueOf(selectedCourier.activeJobs()));
            detailCourierMobile.setText(orDash(selectedCourier.mobilePda()));
            detailCourierTracking.setText(orDash(selectedCourier.trackingLabel()));
            detailCourierAvailable.setText(orDash(formatDateTime(selectedCourier.availableTime())));
        }
    }

    private void clearDetail() {
        selectedJob = null;
        selectedCourier = null;
        updateDetail();
    }

    private void updateMap() {
        long mappedJobs = jobs.stream().filter(TrackingJobRow::hasMapRoute).count();
        long trackedCouriers = couriers.stream().filter(TrackingCourierRow::hasPoint).count();
        if (jobs.isEmpty() && couriers.isEmpty()) {
            mapInfo.setText("Keine Trackingdaten fuer den gewaehlten Tag.");
            mapView.getElement().setProperty("innerHTML", buildEmptyMapSvg("Keine Trackingdaten"));
            return;
        }
        mapInfo.setText(mappedJobs + " geocodierte Auftragsrouten, " + trackedCouriers + " ortbare Kuriere");
        mapView.getElement().setProperty("innerHTML", buildMapSvg());
    }

    private String buildMapSvg() {
        List<PointSource> points = new ArrayList<>();
        for (TrackingJobRow row : jobs) {
            if (row.hasMapRoute()) {
                points.add(new PointSource(row.pickupLatitude(), row.pickupLongitude()));
                points.add(new PointSource(row.deliveryLatitude(), row.deliveryLongitude()));
            }
        }
        for (TrackingCourierRow row : couriers) {
            if (row.hasPoint()) {
                points.add(new PointSource(row.latitude(), row.longitude()));
            }
        }
        if (points.isEmpty()) {
            return buildEmptyMapSvg("Keine geocodierten Trackingdaten");
        }

        double minLongitude = points.stream().mapToDouble(PointSource::longitude).min().orElse(5.5d);
        double maxLongitude = points.stream().mapToDouble(PointSource::longitude).max().orElse(15.5d);
        double minLatitude = points.stream().mapToDouble(PointSource::latitude).min().orElse(47.0d);
        double maxLatitude = points.stream().mapToDouble(PointSource::latitude).max().orElse(55.0d);

        double longitudeSpan = Math.max(maxLongitude - minLongitude, 4.0d);
        double latitudeSpan = Math.max(maxLatitude - minLatitude, 3.0d);
        minLongitude -= longitudeSpan * 0.12d;
        maxLongitude += longitudeSpan * 0.12d;
        minLatitude -= latitudeSpan * 0.12d;
        maxLatitude += latitudeSpan * 0.12d;

        int width = 820;
        int height = 360;
        int padding = 26;
        StringBuilder svg = new StringBuilder();
        svg.append("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ").append(width).append(' ').append(height).append("'>");
        svg.append("<defs>");
        svg.append("<linearGradient id='tracking-bg' x1='0' y1='0' x2='0' y2='1'>")
                .append("<stop offset='0%' stop-color='#edf6ff'/>")
                .append("<stop offset='100%' stop-color='#f7f9fc'/>")
                .append("</linearGradient>");
        svg.append("</defs>");
        svg.append("<rect width='").append(width).append("' height='").append(height).append("' fill='url(#tracking-bg)'/>");
        svg.append("<rect x='").append(padding).append("' y='").append(padding).append("' width='")
                .append(width - (padding * 2)).append("' height='").append(height - (padding * 2))
                .append("' rx='18' fill='#ffffff' fill-opacity='0.55' stroke='#0f172a' stroke-opacity='0.10'/>");

        for (int step = 1; step < 5; step++) {
            double verticalX = padding + ((width - (padding * 2)) / 5.0d) * step;
            double horizontalY = padding + ((height - (padding * 2)) / 5.0d) * step;
            svg.append("<line x1='").append(roundSvg(verticalX)).append("' y1='").append(padding)
                    .append("' x2='").append(roundSvg(verticalX)).append("' y2='").append(height - padding)
                    .append("' stroke='#0f172a' stroke-opacity='0.08' stroke-dasharray='4 8'/>");
            svg.append("<line x1='").append(padding).append("' y1='").append(roundSvg(horizontalY))
                    .append("' x2='").append(width - padding).append("' y2='").append(roundSvg(horizontalY))
                    .append("' stroke='#0f172a' stroke-opacity='0.08' stroke-dasharray='4 8'/>");
        }

        for (TrackingJobRow row : jobs) {
            appendRoute(svg, row, minLongitude, maxLongitude, minLatitude, maxLatitude, width, height, padding);
        }
        for (TrackingCourierRow row : couriers) {
            appendCourierMarker(svg, row, minLongitude, maxLongitude, minLatitude, maxLatitude, width, height, padding);
        }

        svg.append("</svg>");
        return svg.toString();
    }

    private void appendRoute(StringBuilder svg,
                             TrackingJobRow row,
                             double minLongitude,
                             double maxLongitude,
                             double minLatitude,
                             double maxLatitude,
                             int width,
                             int height,
                             int padding) {
        if (!row.hasMapRoute()) {
            return;
        }
        double x1 = projectLongitude(row.pickupLongitude(), minLongitude, maxLongitude, width, padding);
        double y1 = projectLatitude(row.pickupLatitude(), minLatitude, maxLatitude, height, padding);
        double x2 = projectLongitude(row.deliveryLongitude(), minLongitude, maxLongitude, width, padding);
        double y2 = projectLatitude(row.deliveryLatitude(), minLatitude, maxLatitude, height, padding);
        String color = statusColor(row.status());
        boolean selected = selectedJob != null && row.jobId().equals(selectedJob.jobId());
        String tooltip = escapeSvgText("Auftrag " + row.jobId() + " | " + row.pickupLabel() + " -> " + row.deliveryLabel());

        if (selected) {
            svg.append("<line x1='").append(roundSvg(x1)).append("' y1='").append(roundSvg(y1))
                    .append("' x2='").append(roundSvg(x2)).append("' y2='").append(roundSvg(y2))
                    .append("' stroke='#101828' stroke-opacity='0.22' stroke-width='8' stroke-linecap='round'/>");
        }

        svg.append("<line x1='").append(roundSvg(x1)).append("' y1='").append(roundSvg(y1))
                .append("' x2='").append(roundSvg(x2)).append("' y2='").append(roundSvg(y2))
                .append("' stroke='").append(color).append("' stroke-width='").append(selected ? "4.5" : "3")
                .append("' stroke-linecap='round' stroke-opacity='0.88'>");
        svg.append("<title>").append(tooltip).append("</title></line>");
        svg.append("<circle cx='").append(roundSvg(x1)).append("' cy='").append(roundSvg(y1))
                .append("' r='").append(selected ? "5.5" : "4").append("' fill='#ffffff' stroke='")
                .append(color).append("' stroke-width='2'/>");
        svg.append("<circle cx='").append(roundSvg(x2)).append("' cy='").append(roundSvg(y2))
                .append("' r='").append(selected ? "5.5" : "4.5").append("' fill='").append(color)
                .append("' stroke='#ffffff' stroke-width='1.5'/>");
    }

    private void appendCourierMarker(StringBuilder svg,
                                     TrackingCourierRow row,
                                     double minLongitude,
                                     double maxLongitude,
                                     double minLatitude,
                                     double maxLatitude,
                                     int width,
                                     int height,
                                     int padding) {
        if (!row.hasPoint()) {
            return;
        }
        double x = projectLongitude(row.longitude(), minLongitude, maxLongitude, width, padding);
        double y = projectLatitude(row.latitude(), minLatitude, maxLatitude, height, padding);
        boolean selected = selectedCourier != null && row.courierId().equals(selectedCourier.courierId());
        String fill = row.gpsBased() ? "#175cd3" : "#b54708";
        String tooltip = escapeSvgText("Kurier " + row.courierSid() + " | " + row.trackingLabel());

        if (selected) {
            svg.append("<circle cx='").append(roundSvg(x)).append("' cy='").append(roundSvg(y))
                    .append("' r='12' fill='#101828' fill-opacity='0.15'/>");
        }
        svg.append("<circle cx='").append(roundSvg(x)).append("' cy='").append(roundSvg(y))
                .append("' r='").append(selected ? "7" : "5.5").append("' fill='").append(fill)
                .append("' stroke='#ffffff' stroke-width='2'>");
        svg.append("<title>").append(tooltip).append("</title></circle>");
    }

    private String buildEmptyMapSvg(String message) {
        return "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 820 360'>"
                + "<rect width='820' height='360' fill='#f7f9fc'/>"
                + "<rect x='26' y='26' width='768' height='308' rx='18' fill='#ffffff' stroke='#0f172a' stroke-opacity='0.10'/>"
                + "<text x='410' y='180' text-anchor='middle' fill='#475467' font-size='16' font-family='Arial, sans-serif'>"
                + escapeSvgText(message)
                + "</text></svg>";
    }

    private double projectLongitude(Double longitude, double minLongitude, double maxLongitude, int width, int padding) {
        double usableWidth = width - (padding * 2.0d);
        return padding + ((longitude - minLongitude) / (maxLongitude - minLongitude)) * usableWidth;
    }

    private double projectLatitude(Double latitude, double minLatitude, double maxLatitude, int height, int padding) {
        double usableHeight = height - (padding * 2.0d);
        return padding + (1.0d - ((latitude - minLatitude) / (maxLatitude - minLatitude))) * usableHeight;
    }

    private String roundSvg(double value) {
        return String.format(Locale.ROOT, "%.2f", value);
    }

    private String escapeSvgText(String value) {
        return value
                .replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
    }

    private String statusColor(Integer status) {
        if (status == null) {
            return "#667085";
        }
        return switch (status) {
            case 1 -> "#175cd3";
            case 2 -> "#087443";
            case 8 -> "#087443";
            case 9 -> "#b42318";
            default -> "#667085";
        };
    }

    private String formatJobTimes(TrackingJobRow row) {
        List<String> values = new ArrayList<>();
        if (!row.orderTime().isBlank()) {
            values.add("Auftrag " + formatDateTime(row.orderTime()));
        }
        if (!row.takeTime().isBlank()) {
            values.add("Uebernommen " + formatDateTime(row.takeTime()));
        }
        if (!row.finishTime().isBlank()) {
            values.add("Abschluss " + formatDateTime(row.finishTime()));
        }
        return values.isEmpty() ? "" : String.join(" / ", values);
    }

    private String formatDateTime(String value) {
        return value == null || value.isBlank() ? "" : value.replace("T", " ");
    }

    private String formatAmount(BigDecimal value) {
        return value != null ? value.toPlainString() : "";
    }

    private String orDash(String value) {
        return value == null || value.isBlank() ? "-" : value;
    }

    private Long toLong(Object value) {
        if (value instanceof Number number) {
            return number.longValue();
        }
        try {
            return value != null ? Long.parseLong(value.toString()) : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Integer toInteger(Object value) {
        if (value instanceof Number number) {
            return number.intValue();
        }
        try {
            return value != null ? Integer.parseInt(value.toString()) : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Double toDouble(Object value) {
        if (value instanceof Number number) {
            return number.doubleValue();
        }
        try {
            return value != null ? Double.parseDouble(value.toString()) : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private BigDecimal toBigDecimal(Object value) {
        if (value instanceof BigDecimal decimal) {
            return decimal;
        }
        try {
            return value != null ? new BigDecimal(value.toString()) : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private boolean bool(Object value) {
        return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(str(value));
    }

    private String str(Object value) {
        return value != null ? value.toString() : "";
    }

    private String str(Object value, Object fallback) {
        String text = str(value);
        return text.isBlank() ? String.valueOf(fallback) : text;
    }

    private record CustomerOption(Long id, String label) {
    }

    private record PointSource(Double latitude, Double longitude) {
    }

    private record TrackingJobRow(Long jobId,
                                  Integer status,
                                  String statusLabel,
                                  String orderTime,
                                  String takeTime,
                                  String finishTime,
                                  Long customerId,
                                  String customerName,
                                  String costCenterName,
                                  String commissionNo,
                                  String pickupName,
                                  String pickupZipcode,
                                  String pickupCity,
                                  Double pickupLatitude,
                                  Double pickupLongitude,
                                  String deliveryName,
                                  String deliveryZipcode,
                                  String deliveryCity,
                                  Double deliveryLatitude,
                                  Double deliveryLongitude,
                                  Long courierId,
                                  String courierSid,
                                  String courierCompanyName,
                                  String courierVehicleSid,
                                  String vehicleTypeName,
                                  Double courierLatitude,
                                  Double courierLongitude,
                                  String courierGpsTime,
                                  String courierGpsTypeLabel,
                                  String courierLocationZipcode,
                                  boolean courierTracked,
                                  boolean courierGpsBased,
                                  BigDecimal totalPrice) {

        private boolean hasMapRoute() {
            return pickupLatitude != null && pickupLongitude != null && deliveryLatitude != null && deliveryLongitude != null;
        }

        private String pickupLabel() {
            return joinNonBlank(pickupName, joinNonBlank(pickupZipcode, pickupCity));
        }

        private String deliveryLabel() {
            return joinNonBlank(deliveryName, joinNonBlank(deliveryZipcode, deliveryCity));
        }

        private String trackingLabel() {
            if (!courierTracked) {
                return "";
            }
            String mode = courierGpsBased ? "GPS" : "PLZ";
            String location = courierLocationZipcode != null && !courierLocationZipcode.isBlank() ? " / " + courierLocationZipcode : "";
            String time = courierGpsTime != null && !courierGpsTime.isBlank() ? " / " + courierGpsTime.replace("T", " ") : "";
            return mode + location + time;
        }
    }

    private record TrackingCourierRow(Long courierId,
                                      String courierSid,
                                      String companyName,
                                      String vehicleDisplay,
                                      int activeJobs,
                                      String locationZipcode,
                                      Double latitude,
                                      Double longitude,
                                      String gpsTime,
                                      String gpsTypeLabel,
                                      String mobilePda,
                                      String availableTime,
                                      boolean tracked,
                                      boolean gpsBased) {

        private boolean hasPoint() {
            return latitude != null && longitude != null;
        }

        private String trackingLabel() {
            if (!tracked) {
                return "";
            }
            String mode = gpsBased ? "GPS" : "PLZ";
            String zipcode = locationZipcode != null && !locationZipcode.isBlank() ? " / " + locationZipcode : "";
            String time = gpsTime != null && !gpsTime.isBlank() ? " / " + gpsTime.replace("T", " ") : "";
            return mode + zipcode + time;
        }
    }

    private static String joinNonBlank(String left, String right) {
        String leftValue = left != null ? left : "";
        String rightValue = right != null ? right : "";
        if (leftValue.isBlank()) {
            return rightValue;
        }
        if (rightValue.isBlank()) {
            return leftValue;
        }
        return leftValue + " / " + rightValue;
    }
}
