package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.dto.JobPricingResultDto;
import de.votian.services.dto.JobTourServiceDraftRowDto;
import de.votian.services.dto.TourDetailDto;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Headquarters;
import de.votian.services.entity.Job;
import de.votian.services.entity.JobPrice;
import de.votian.services.entity.MetaType;
import de.votian.services.entity.ServiceEntity;
import de.votian.services.entity.ServiceHistory;
import de.votian.services.entity.ServicePriceHistory;
import de.votian.services.entity.ServiceType;
import de.votian.services.entity.ServiceZipCode;
import de.votian.services.entity.ServiceZipCodeArea;
import de.votian.services.entity.ServiceZipCodeAreaAddress;
import de.votian.services.entity.ServiceZipCodeAreaHistory;
import de.votian.services.entity.ServiceZipCodeHistory;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.CustomerRepository;
import de.votian.services.repository.GenericDataContainerRepository;
import de.votian.services.repository.HeadquartersRepository;
import de.votian.services.repository.MetaTypeRepository;
import de.votian.services.repository.ServiceEntityRepository;
import de.votian.services.repository.ServiceHistoryRepository;
import de.votian.services.repository.ServicePriceHistoryRepository;
import de.votian.services.repository.ServiceTypeRepository;
import de.votian.services.repository.ServiceZipCodeAreaAddressRepository;
import de.votian.services.repository.ServiceZipCodeAreaHistoryRepository;
import de.votian.services.repository.ServiceZipCodeAreaMappingRepository;
import de.votian.services.repository.ServiceZipCodeAreaRepository;
import de.votian.services.repository.ServiceZipCodeHistoryRepository;
import de.votian.services.repository.ServiceZipCodeRepository;
import org.springframework.stereotype.Service;

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

@Service
public class JobPricingService {

    static final int JB_CR_MARKUP = 1;
    static final int JB_CR_FIXPRICE = 2;
    static final int JB_CR_FIXSERVICEPRICE = 3;
    static final int JB_CR_SERVICEPRICE = 4;
    static final int JB_CR_PROV = 5;
    static final int JB_CR_SERVICE_PROV = 6;
    static final int JB_CR_AGIO = 7;
    static final int JB_MARKUP_MAN = 8;
    static final int JB_CR_MARKUP_MAN = 9;
    static final int JB_CR_SUBTOTAL = 10;
    static final int JB_SELF_SERVICE_DISCOUNT = 13;

    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    private static final BigDecimal PLACEHOLDER_ZERO = new BigDecimal("0.01");
    private static final String VEHICLE_TYPE_META = "vehicletype";
    private static final String BASIC_PRICE_SERVICE = "Grundpreis";
    private static final String SERVICEPRICE_SERVICE_NAME = "Servicepreis";
    private static final String FUEL_MARKUP_SERVICE = "Aufschlag<br>Sprit [%]";
    private static final String GLOBAL_COURIER_DISCOUNT_SERVICE = "Globaler<br>Frachtverg.-Abschlag [%]";
    private static final String GLOBAL_COURIER_PRICE_RATE_SERVICE = "Globaler<br>Fuhrlohnabschlag [%]";
    private static final String CUSTOMER_OBJECT_TYPE = "cs";
    private static final String CUSTOMER_FUEL_MARKUP_FIELD = "cs_markup_prov";
    private static final String COURIER_OBJECT_TYPE = "cr";
    private static final String COURIER_CUSTOMER_AGIO_FIELD = "cscr_prov";
    private static final String JOB_OBJECT_TYPE = "jb";
    private static final String AUTOMATIC_STREET_SERVICE_PRICE_FIELD = "jb_serviceprice_street_auto";

    private final CostCenterRepository costCenterRepository;
    private final CustomerRepository customerRepository;
    private final HeadquartersRepository headquartersRepository;
    private final MetaTypeRepository metaTypeRepository;
    private final ServiceEntityRepository serviceEntityRepository;
    private final ServiceTypeRepository serviceTypeRepository;
    private final ServiceHistoryRepository serviceHistoryRepository;
    private final ServicePriceHistoryRepository servicePriceHistoryRepository;
    private final ServiceZipCodeRepository serviceZipCodeRepository;
    private final ServiceZipCodeHistoryRepository serviceZipCodeHistoryRepository;
    private final ServiceZipCodeAreaRepository serviceZipCodeAreaRepository;
    private final ServiceZipCodeAreaAddressRepository serviceZipCodeAreaAddressRepository;
    private final ServiceZipCodeAreaHistoryRepository serviceZipCodeAreaHistoryRepository;
    private final ServiceZipCodeAreaMappingRepository serviceZipCodeAreaMappingRepository;
    private final GenericDataContainerRepository genericDataContainerRepository;
    private final ParameterService parameterService;

    public JobPricingService(CostCenterRepository costCenterRepository,
                             CustomerRepository customerRepository,
                             HeadquartersRepository headquartersRepository,
                             MetaTypeRepository metaTypeRepository,
                             ServiceEntityRepository serviceEntityRepository,
                             ServiceTypeRepository serviceTypeRepository,
                             ServiceHistoryRepository serviceHistoryRepository,
                             ServicePriceHistoryRepository servicePriceHistoryRepository,
                             ServiceZipCodeRepository serviceZipCodeRepository,
                             ServiceZipCodeHistoryRepository serviceZipCodeHistoryRepository,
                             ServiceZipCodeAreaRepository serviceZipCodeAreaRepository,
                             ServiceZipCodeAreaAddressRepository serviceZipCodeAreaAddressRepository,
                             ServiceZipCodeAreaHistoryRepository serviceZipCodeAreaHistoryRepository,
                             ServiceZipCodeAreaMappingRepository serviceZipCodeAreaMappingRepository,
                             GenericDataContainerRepository genericDataContainerRepository,
                             ParameterService parameterService) {
        this.costCenterRepository = costCenterRepository;
        this.customerRepository = customerRepository;
        this.headquartersRepository = headquartersRepository;
        this.metaTypeRepository = metaTypeRepository;
        this.serviceEntityRepository = serviceEntityRepository;
        this.serviceTypeRepository = serviceTypeRepository;
        this.serviceHistoryRepository = serviceHistoryRepository;
        this.servicePriceHistoryRepository = servicePriceHistoryRepository;
        this.serviceZipCodeRepository = serviceZipCodeRepository;
        this.serviceZipCodeHistoryRepository = serviceZipCodeHistoryRepository;
        this.serviceZipCodeAreaRepository = serviceZipCodeAreaRepository;
        this.serviceZipCodeAreaAddressRepository = serviceZipCodeAreaAddressRepository;
        this.serviceZipCodeAreaHistoryRepository = serviceZipCodeAreaHistoryRepository;
        this.serviceZipCodeAreaMappingRepository = serviceZipCodeAreaMappingRepository;
        this.genericDataContainerRepository = genericDataContainerRepository;
        this.parameterService = parameterService;
    }

    public BigDecimal calculateAutomaticStreetServicePrice(Job job, List<TourDetailDto> tours) {
        return scale(resolveCalculatedAutomaticStreetServicePrice(job, tours));
    }

    public JobPricingResultDto calculateDraft(Job draft, List<JobPrice> inputPrices) {
        return calculateDraft(draft, inputPrices, List.of(), false);
    }

    public JobPricingResultDto calculateDraft(Job draft, List<JobPrice> inputPrices, List<TourDetailDto> tours) {
        return calculateDraft(draft, inputPrices, tours, false);
    }

    public JobPricingResultDto calculateDraft(Job draft,
                                              List<JobPrice> inputPrices,
                                              List<TourDetailDto> tours,
                                              boolean manualCourierTotalOverride) {
        Job job = draft != null ? draft : new Job();
        Map<Integer, JobPrice> prices = toPriceMap(inputPrices);
        Integer courierServicePriceMetaSort = resolveCourierServicePriceMetaSort(prices);

        Customer customer = resolveCustomer(job);
        Long customerId = customer != null ? customer.getId() : resolveCustomerId(job);
        Long hqId = job.getHeadquartersId() != null
                ? job.getHeadquartersId()
                : customer != null ? customer.getHeadquartersId() : null;
        if (job.getHeadquartersId() == null && hqId != null) {
            job.setHeadquartersId(hqId);
        }

        BigDecimal discountRate = resolveDiscountRate(job, customer);
        BigDecimal selfServiceDiscount = resolveSelfServiceDiscount(prices, hqId);
        boolean servicePriceDiscountApplied = isServicePriceDiscountApplied(hqId);
        BigDecimal courierProvisionRate = money(value(prices, JB_CR_PROV));
        BigDecimal explicitCourierProvisionRate = courierProvisionRate;
        LocalDateTime pricingDateTime = job.getOrderTime() != null ? job.getOrderTime() : LocalDateTime.now();

        Integer mappedVehicleTypeId = resolveMappedVehicleTypeId(job.getVehicleTypeId(), hqId);
        String vehicleTypeName = resolveVehicleTypeName(mappedVehicleTypeId);
        BigDecimal orderMarkup = resolveOrderMarkup(job, prices, customer, customerId, hqId, pricingDateTime, vehicleTypeName);
        BigDecimal courierMarkup = resolveCourierMarkup(prices, customer, customerId, hqId, pricingDateTime, vehicleTypeName);
        boolean courierPriceModeEnabled = isCourierPriceModeEnabled(hqId);
        boolean courierPriceModeActive = isCourierPriceModeActive(hqId, pricingDateTime);
        boolean includeCourierServiceInCourierTotals = !courierPriceModeEnabled || courierPriceModeActive;
        BigDecimal automaticStreetServicePrice = resolveAutomaticStreetServicePrice(job, tours, pricingDateTime);
        BigDecimal baseServicePrice = resolveBaseServicePrice(job, automaticStreetServicePrice);
        BigDecimal totalServicePrice = baseServicePrice.add(automaticStreetServicePrice);

        CourierServicePricing courierServicePricing = resolveCourierServicePricing(
                prices,
                baseServicePrice,
                explicitCourierProvisionRate,
                discountRate,
                servicePriceDiscountApplied,
                customerId,
                hqId,
                pricingDateTime,
                vehicleTypeName
        );
        BigDecimal courierServicePrice = courierServicePricing.price();
        BigDecimal courierServiceProvisionRate = courierServicePricing.rate();
        boolean hasManualCourierServicePriceInput = hasManualCourierServicePrice(prices);
        BigDecimal courierAgio = manualCourierTotalOverride
                ? money(value(prices, JB_CR_AGIO))
                : resolveCourierAgio(prices, job, customerId, hqId, pricingDateTime);
        BigDecimal manualCourierFixPrice = resolveExplicitCourierFixPrice(prices);
        boolean hasManualCourierFixPrice = hasAmount(manualCourierFixPrice);
        if (hasManualCourierFixPrice) {
            courierProvisionRate = BigDecimal.ZERO;
            courierAgio = BigDecimal.ZERO;
        }
        if (hasManualCourierServicePriceInput) {
            courierServiceProvisionRate = BigDecimal.ZERO;
        }
        boolean hasExplicitCourierSubtotal = hasExplicitCourierSubtotal(job, prices);
        BigDecimal manualCourierSubtotal = resolveCourierSubtotal(job, prices);
        BigDecimal manualCourierTotalSubtotal = resolveManualCourierTotalSubtotal(
                job,
                courierMarkup,
                courierAgio,
                courierServicePrice,
                includeCourierServiceInCourierTotals
        );
        boolean hasManualCourierTotalOverrideActive = manualCourierTotalOverride
                && !hasManualCourierFixPrice
                && !hasExplicitCourierSubtotal
                && hasAmount(manualCourierTotalSubtotal);
        if (hasManualCourierTotalOverrideActive) {
            manualCourierSubtotal = manualCourierTotalSubtotal;
        }

        RoutePricing routePricing = calculateRoutePricing(
                job,
                customerId,
                hqId,
                tours,
                pricingDateTime,
                mappedVehicleTypeId,
                vehicleTypeName,
                discountRate,
                selfServiceDiscount,
                orderMarkup,
                courierMarkup
        );

        BigDecimal subtotal = money(job.getSubtotalPrice());
        BigDecimal total = money(job.getTotalPrice());
        BigDecimal courierSubtotal = manualCourierSubtotal;
        BigDecimal routeCourierTotal = BigDecimal.ZERO;

        BigDecimal customerServicePrice = servicePriceContribution(
                baseServicePrice,
                discountRate,
                servicePriceDiscountApplied
        ).add(automaticStreetServicePrice);

        if (hasAmount(job.getFixPrice())) {
            BigDecimal fixSubtotal = applyDiscount(money(job.getFixPrice()), discountRate);
            BigDecimal discountedSubtotal = applyDiscount(fixSubtotal, selfServiceDiscount);
            BigDecimal discountedTotal = applyDiscount(applyMarkup(fixSubtotal, orderMarkup), selfServiceDiscount);

            subtotal = discountedSubtotal.add(customerServicePrice);
            total = discountedTotal.add(customerServicePrice);

            if (hasManualCourierFixPrice) {
                courierSubtotal = applyDiscount(manualCourierFixPrice, selfServiceDiscount);
            } else if (hasManualCourierTotalOverrideActive) {
                courierSubtotal = manualCourierTotalSubtotal;
            } else {
                if (!hasAmount(courierSubtotal) && hasAmount(courierProvisionRate)) {
                    courierSubtotal = applyDiscount(fixSubtotal, courierProvisionRate);
                }
                courierSubtotal = applyDiscount(money(courierSubtotal), discountRate);
                courierSubtotal = applyDiscount(courierSubtotal, selfServiceDiscount);
            }
        } else if (routePricing.hasAnyPrice()) {
            subtotal = routePricing.customerSubtotal.add(customerServicePrice);
            total = routePricing.customerTotal.add(customerServicePrice);
            if (hasManualCourierFixPrice) {
                courierSubtotal = applyDiscount(manualCourierFixPrice, selfServiceDiscount);
                routeCourierTotal = applyMarkup(courierSubtotal, courierMarkup);
            } else if (hasManualCourierTotalOverrideActive) {
                courierSubtotal = manualCourierTotalSubtotal;
                routeCourierTotal = scale(money(job.getCourierPrice()));
            } else {
                courierSubtotal = routePricing.courierSubtotal;
                routeCourierTotal = routePricing.courierTotal;
                BigDecimal globalCourierPriceRate = resolveGlobalCourierPriceRate(customerId, hqId, pricingDateTime, vehicleTypeName);
                if (!hasAmount(courierProvisionRate) && hasAmount(globalCourierPriceRate)) {
                    courierProvisionRate = globalCourierPriceRate;
                } else if (!hasAmount(courierProvisionRate) && hasAmount(routePricing.customerSubtotal) && hasAmount(routePricing.courierSubtotal)) {
                    courierProvisionRate = percentDifference(routePricing.customerSubtotal, routePricing.courierSubtotal);
                }
            }
        } else {
            if (!hasAmount(total) && hasAmount(subtotal)) {
                total = hasAmount(orderMarkup) ? applyMarkup(subtotal, orderMarkup) : subtotal;
            }
            if (!hasAmount(subtotal) && hasAmount(total)) {
                subtotal = total;
            }
            if (hasManualCourierFixPrice) {
                courierSubtotal = applyDiscount(manualCourierFixPrice, selfServiceDiscount);
            } else if (hasManualCourierTotalOverrideActive) {
                courierSubtotal = manualCourierTotalSubtotal;
            } else if (!hasAmount(courierSubtotal) && hasAmount(courierProvisionRate) && hasAmount(subtotal)) {
                courierSubtotal = applyDiscount(subtotal, courierProvisionRate);
            }
        }

        if (!hasManualCourierFixPrice
                && !hasManualCourierTotalOverrideActive
                && !hasAmount(courierProvisionRate)
                && hasAmount(subtotal)
                && hasAmount(courierSubtotal)) {
            BigDecimal provisionSource = routePricing.hasAnyPrice()
                    ? subtotal.subtract(customerServicePrice)
                    : subtotal;
            courierProvisionRate = percentDifference(provisionSource, courierSubtotal);
        }
        boolean courierAgioApplied = !hasManualCourierTotalOverrideActive
                && hasAmount(courierAgio)
                && hasAmount(courierProvisionRate);

        if (courierAgioApplied) {
            BigDecimal effectiveProvisionRate = courierProvisionRate.subtract(courierAgio);
            if (routePricing.hasAnyPrice()) {
                CourierSubtotalPricing adjustedRoutePricing = applyRouteCourierProvisionRate(
                        routePricing,
                        effectiveProvisionRate,
                        courierMarkup
                );
                courierSubtotal = adjustedRoutePricing.subtotal();
                routeCourierTotal = adjustedRoutePricing.total();
            } else {
                BigDecimal subtotalWithoutService = subtotal.subtract(customerServicePrice);
                courierSubtotal = applyDiscount(subtotalWithoutService, effectiveProvisionRate);
            }

            if (hasAmount(baseServicePrice) && hasAmount(courierServiceProvisionRate)) {
                courierServicePrice = deriveCourierServicePrice(
                        baseServicePrice,
                        courierServiceProvisionRate.subtract(courierAgio),
                        discountRate,
                        servicePriceDiscountApplied
                );
            }
        }

        BigDecimal courierTotal = BigDecimal.ZERO;
        if (routePricing.hasAnyPrice()) {
            courierTotal = routeCourierTotal;
            if (includeCourierServiceInCourierTotals) {
                courierTotal = courierTotal.add(money(courierServicePrice));
            }
        } else if (hasAmount(courierSubtotal) || hasAmount(courierServicePrice)) {
            courierTotal = applyMarkup(money(courierSubtotal), courierMarkup);
            if (includeCourierServiceInCourierTotals) {
                courierTotal = courierTotal.add(money(courierServicePrice));
            }
        }
        if (hasManualCourierTotalOverrideActive && hasAmount(job.getCourierPrice())) {
            courierTotal = scale(money(job.getCourierPrice()));
        }
        BigDecimal derivedCourierSubtotalPrice = courierSubtotal;
        if (courierPriceModeActive && hasAmount(courierServicePrice)) {
            derivedCourierSubtotalPrice = money(courierSubtotal).add(courierServicePrice);
        }

        job.setMarkup(scale(orderMarkup));
        job.setServicePrice(scale(totalServicePrice));
        job.setSubtotalPrice(scale(subtotal));
        job.setTotalPrice(scale(total));
        job.setCourierSubprice(scale(courierSubtotal));
        job.setCourierPrice(scale(courierTotal));

        syncDerivedPrice(prices, JB_MARKUP_MAN, scale(orderMarkup), existingRemark(prices, JB_MARKUP_MAN), existingReference(prices, JB_MARKUP_MAN));
        syncDerivedPrice(prices, JB_CR_SUBTOTAL, scale(derivedCourierSubtotalPrice), existingRemark(prices, JB_CR_SUBTOTAL), existingReference(prices, JB_CR_SUBTOTAL));
        syncDerivedPrice(
                prices,
                JB_SELF_SERVICE_DISCOUNT,
                scale(selfServiceDiscount),
                hasAmount(selfServiceDiscount)
                        ? nullable(scale(applyMarkup(applyDiscount(money(job.getFixPrice()), discountRate), orderMarkup)))
                        : existingRemark(prices, JB_SELF_SERVICE_DISCOUNT),
                existingReference(prices, JB_SELF_SERVICE_DISCOUNT)
        );

        if (hasAmount(courierProvisionRate) || prices.containsKey(JB_CR_PROV)) {
            syncDerivedPrice(prices, JB_CR_PROV, scale(courierProvisionRate), existingRemark(prices, JB_CR_PROV), existingReference(prices, JB_CR_PROV));
        }
        if (hasAmount(courierServiceProvisionRate) || prices.containsKey(JB_CR_SERVICE_PROV)) {
            syncDerivedPrice(prices, JB_CR_SERVICE_PROV, scale(courierServiceProvisionRate), existingRemark(prices, JB_CR_SERVICE_PROV), existingReference(prices, JB_CR_SERVICE_PROV));
        }
        if (hasAmount(courierAgio) || prices.containsKey(JB_CR_AGIO)) {
            syncDerivedPrice(prices, JB_CR_AGIO, scale(courierAgio), existingRemark(prices, JB_CR_AGIO), existingReference(prices, JB_CR_AGIO));
        }
        if (hasAmount(courierServicePrice)
                || prices.containsKey(JB_CR_FIXSERVICEPRICE)
                || prices.containsKey(JB_CR_SERVICEPRICE)) {
            syncDerivedPrice(
                    prices,
                    courierServicePriceMetaSort,
                    scale(courierServicePrice),
                    existingRemark(prices, courierServicePriceMetaSort),
                    existingReference(prices, courierServicePriceMetaSort)
            );
        }

        JobPricingResultDto result = new JobPricingResultDto();
        result.setJob(job);
        result.setPrices(prices.values().stream()
                .sorted(Comparator.comparing(JobPrice::getMetaSort, Comparator.nullsLast(Integer::compareTo)))
                .toList());
        result.setTourServices(buildPersistedTourServiceRows(
                job,
                customerId,
                hqId,
                tours,
                pricingDateTime,
                mappedVehicleTypeId,
                vehicleTypeName,
                discountRate,
                orderMarkup,
                servicePriceDiscountApplied,
                baseServicePrice,
                automaticStreetServicePrice
        ));
        result.setDiscountRate(scale(discountRate));
        result.setSelfServiceDiscount(scale(selfServiceDiscount));
        result.setBaseServicePrice(scale(baseServicePrice));
        result.setAutomaticStreetServicePrice(scale(automaticStreetServicePrice));
        result.setServicePriceDiscountApplied(servicePriceDiscountApplied);
        return result;
    }

    private RoutePricing calculateRoutePricing(Job job,
                                               Long customerId,
                                               Long hqId,
                                               List<TourDetailDto> tours,
                                               LocalDateTime pricingDateTime,
                                               Integer mappedVehicleTypeId,
                                               String vehicleTypeName,
                                               BigDecimal discountRate,
                                               BigDecimal selfServiceDiscount,
                                               BigDecimal orderMarkup,
                                               BigDecimal courierMarkup) {
        int invoiceMode = resolveInvoiceMode(job, customerId, hqId);
        List<TourDetailDto> routeStops = normalizeTours(tours);
        if (invoiceMode <= 0 || routeStops.size() < 2 || hqId == null || vehicleTypeName.isBlank()) {
            return RoutePricing.empty();
        }

        BigDecimal customerSubtotal = BigDecimal.ZERO;
        BigDecimal customerTotal = BigDecimal.ZERO;
        BigDecimal courierSubtotal = BigDecimal.ZERO;
        BigDecimal courierTotal = BigDecimal.ZERO;
        BigDecimal courierMarkupEligibleCustomerSubtotal = BigDecimal.ZERO;
        BigDecimal courierMarkupExemptCustomerSubtotal = BigDecimal.ZERO;
        boolean applyBasicMarkup = !isMarkupSuppressedForBasicPrice(hqId);

        PriceComponent basicComponent = resolveServiceComponent(
                BASIC_PRICE_SERVICE,
                vehicleTypeName,
                customerId,
                hqId,
                pricingDateTime
        );
        PriceTotals basicTotals = applyComponentPricing(
                basicComponent,
                customerId,
                hqId,
                pricingDateTime,
                mappedVehicleTypeId,
                vehicleTypeName,
                discountRate,
                selfServiceDiscount,
                orderMarkup,
                applyBasicMarkup,
                courierMarkup,
                applyBasicMarkup
        );
        customerSubtotal = customerSubtotal.add(basicTotals.customerSubtotal);
        customerTotal = customerTotal.add(basicTotals.customerTotal);
        courierSubtotal = courierSubtotal.add(basicTotals.courierSubtotal);
        courierTotal = courierTotal.add(basicTotals.courierTotal);
        if (applyBasicMarkup) {
            courierMarkupEligibleCustomerSubtotal = courierMarkupEligibleCustomerSubtotal.add(basicTotals.customerSubtotal);
        } else {
            courierMarkupExemptCustomerSubtotal = courierMarkupExemptCustomerSubtotal.add(basicTotals.customerSubtotal);
        }

        for (int index = 1; index < routeStops.size(); index++) {
            TourDetailDto from = routeStops.get(index - 1);
            TourDetailDto to = routeStops.get(index);
            ZipPair zipPair = resolveZipPair(from, to);
            if (zipPair == null) {
                continue;
            }
            PriceComponent segmentComponent = invoiceMode == 1
                    ? resolveZipCodeComponent(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId)
                    : resolveAreaComponent(from, to, zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId);
            PriceTotals segmentTotals = applyComponentPricing(
                    segmentComponent,
                    customerId,
                    hqId,
                    pricingDateTime,
                    mappedVehicleTypeId,
                    vehicleTypeName,
                    discountRate,
                    selfServiceDiscount,
                    orderMarkup,
                    true,
                    courierMarkup,
                    true
            );
            customerSubtotal = customerSubtotal.add(segmentTotals.customerSubtotal);
            customerTotal = customerTotal.add(segmentTotals.customerTotal);
            courierSubtotal = courierSubtotal.add(segmentTotals.courierSubtotal);
            courierTotal = courierTotal.add(segmentTotals.courierTotal);
            courierMarkupEligibleCustomerSubtotal = courierMarkupEligibleCustomerSubtotal.add(segmentTotals.customerSubtotal);
        }

        return new RoutePricing(
                scale(customerSubtotal),
                scale(customerTotal),
                scale(courierSubtotal),
                scale(courierTotal),
                scale(courierMarkupEligibleCustomerSubtotal),
                scale(courierMarkupExemptCustomerSubtotal)
        );
    }

    private PriceTotals applyComponentPricing(PriceComponent component,
                                              Long customerId,
                                              Long hqId,
                                              LocalDateTime pricingDateTime,
                                              Integer mappedVehicleTypeId,
                                              String vehicleTypeName,
                                              BigDecimal defaultDiscountRate,
                                              BigDecimal selfServiceDiscount,
                                              BigDecimal orderMarkup,
                                              boolean applyOrderMarkup,
                                              BigDecimal courierMarkup,
                                              boolean applyCourierMarkup) {
        if (component == null || !component.hasAnyPrice()) {
            return PriceTotals.empty();
        }

        BigDecimal effectiveDiscount = hasAmount(defaultDiscountRate)
                ? defaultDiscountRate
                : money(component.discount);

        BigDecimal customerSubtotal = applyDiscount(component.price, effectiveDiscount);
        BigDecimal customerTotal = applyOrderMarkup
                ? applyMarkup(customerSubtotal, orderMarkup)
                : customerSubtotal;

        BigDecimal courierBase = money(component.courierPrice);
        if (!hasAmount(courierBase) && hasAmount(component.price)) {
            BigDecimal globalCourierDiscountRate = resolveGlobalCourierDiscountRate(
                    customerId,
                    hqId,
                    pricingDateTime,
                    vehicleTypeName
            );
            if (hasAmount(globalCourierDiscountRate)) {
                courierBase = applyDiscount(component.price, globalCourierDiscountRate);
            }
        }

        BigDecimal courierSubtotal = applyDiscount(courierBase, effectiveDiscount.add(money(selfServiceDiscount)));
        BigDecimal courierTotal = applyCourierMarkup
                ? applyMarkup(courierSubtotal, courierMarkup)
                : courierSubtotal;
        return new PriceTotals(scale(customerSubtotal), scale(customerTotal), scale(courierSubtotal), scale(courierTotal));
    }

    private List<JobTourServiceDraftRowDto> buildPersistedTourServiceRows(Job job,
                                                                          Long customerId,
                                                                          Long hqId,
                                                                          List<TourDetailDto> tours,
                                                                          LocalDateTime pricingDateTime,
                                                                          Integer mappedVehicleTypeId,
                                                                          String vehicleTypeName,
                                                                          BigDecimal discountRate,
                                                                          BigDecimal orderMarkup,
                                                                          boolean servicePriceDiscountApplied,
                                                                          BigDecimal baseServicePrice,
                                                                          BigDecimal automaticStreetServicePrice) {
        if (job == null || hasAmount(job.getFixPrice())) {
            return List.of();
        }

        int invoiceMode = resolveInvoiceMode(job, customerId, hqId);
        List<TourDetailDto> routeStops = normalizeTours(tours);
        if (invoiceMode <= 0 || routeStops.size() < 2 || hqId == null || vehicleTypeName.isBlank()) {
            return List.of();
        }

        List<JobTourServiceDraftRowDto> rows = new ArrayList<>();
        boolean applyBasicMarkup = !isMarkupSuppressedForBasicPrice(hqId);
        PriceComponent basicComponent = resolveServiceComponent(
                BASIC_PRICE_SERVICE,
                vehicleTypeName,
                customerId,
                hqId,
                pricingDateTime
        );
        addPersistedTourServiceRow(rows, 0, BASIC_PRICE_SERVICE, vehicleTypeName, basicComponent, discountRate, orderMarkup, applyBasicMarkup);

        for (int index = 1; index < routeStops.size(); index++) {
            TourDetailDto from = routeStops.get(index - 1);
            TourDetailDto to = routeStops.get(index);
            ZipPair zipPair = resolveZipPair(from, to);
            if (zipPair == null) {
                continue;
            }
            PersistedRouteSegment segment = invoiceMode == 1
                    ? resolvePersistedZipSegment(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId)
                    : resolvePersistedAreaSegment(from, to, zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId);
            if (segment == null) {
                continue;
            }
            addPersistedTourServiceRow(rows, index, segment.serviceName(), "", segment.component(), discountRate, orderMarkup, true);
        }

        BigDecimal billedServicePrice = servicePriceContribution(
                amountOrZero(baseServicePrice),
                discountRate,
                servicePriceDiscountApplied
        ).add(amountOrZero(automaticStreetServicePrice));
        if (hasAmount(billedServicePrice)) {
            BigDecimal serviceDiscount = hasAmount(automaticStreetServicePrice)
                    ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                    : servicePriceDiscountApplied ? scale(discountRate) : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            BigDecimal serviceRowPrice = hasAmount(automaticStreetServicePrice)
                    ? scale(billedServicePrice)
                    : scale(amountOrZero(baseServicePrice));
            rows.add(createTourServiceDraftRow(0, SERVICEPRICE_SERVICE_NAME, "", serviceRowPrice, serviceDiscount));
        }
        return rows;
    }

    private PersistedRouteSegment resolvePersistedZipSegment(ZipPair zipPair,
                                                             Long customerId,
                                                             Long hqId,
                                                             LocalDateTime pricingDateTime,
                                                             Integer mappedVehicleTypeId) {
        PriceComponent component = resolveZipCodeComponent(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId);
        if (component == null || !component.hasAnyPrice()) {
            return null;
        }
        return new PersistedRouteSegment(component, formatZipServiceName(zipPair));
    }

    private PersistedRouteSegment resolvePersistedAreaSegment(TourDetailDto from,
                                                              TourDetailDto to,
                                                              ZipPair zipPair,
                                                              Long customerId,
                                                              Long hqId,
                                                              LocalDateTime pricingDateTime,
                                                              Integer mappedVehicleTypeId) {
        Long fromAreaId = resolveAreaId(from, zipPair.from, hqId);
        Long toAreaId = resolveAreaId(to, zipPair.to, hqId);
        if (fromAreaId == null || toAreaId == null) {
            return isInvmodeFallbackEnabled(hqId)
                    ? resolvePersistedZipSegment(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId)
                    : null;
        }

        ServiceZipCodeAreaHistory customerHistory = loadAreaHistory(
                fromAreaId,
                toAreaId,
                customerId,
                hqId,
                pricingDateTime,
                mappedVehicleTypeId
        );
        ServiceZipCodeAreaHistory commonHistory = customerId != null && customerId > 0
                ? loadAreaHistory(fromAreaId, toAreaId, 0L, hqId, pricingDateTime, mappedVehicleTypeId)
                : null;
        PriceComponent component = mergePriceComponents(customerHistory, commonHistory);
        if ((component == null || !component.hasAnyPrice()) && isInvmodeFallbackEnabled(hqId)) {
            return resolvePersistedZipSegment(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId);
        }
        if (component == null || !component.hasAnyPrice()) {
            return null;
        }
        return new PersistedRouteSegment(component, formatAreaServiceName(fromAreaId, toAreaId));
    }

    private void addPersistedTourServiceRow(List<JobTourServiceDraftRowDto> rows,
                                            int tourSort,
                                            String serviceName,
                                            String serviceTypeName,
                                            PriceComponent component,
                                            BigDecimal discountRate,
                                            BigDecimal orderMarkup,
                                            boolean applyMarkup) {
        if (rows == null || component == null || !hasAmount(component.price)) {
            return;
        }
        BigDecimal effectiveDiscount = hasAmount(discountRate)
                ? scale(discountRate)
                : scale(money(component.discount));
        BigDecimal persistedPrice = applyMarkup ? applyMarkup(component.price, orderMarkup) : scale(component.price);
        if (!hasAmount(persistedPrice)) {
            return;
        }
        rows.add(createTourServiceDraftRow(tourSort, serviceName, serviceTypeName, persistedPrice, effectiveDiscount));
    }

    private JobTourServiceDraftRowDto createTourServiceDraftRow(int tourSort,
                                                                String serviceName,
                                                                String serviceTypeName,
                                                                BigDecimal price,
                                                                BigDecimal discount) {
        JobTourServiceDraftRowDto row = new JobTourServiceDraftRowDto();
        row.setTourSort(tourSort);
        row.setServiceName(serviceName);
        row.setServiceTypeName(serviceTypeName != null ? serviceTypeName : "");
        row.setPrice(scale(price));
        row.setDiscount(scale(discount));
        return row;
    }

    private String formatZipServiceName(ZipPair zipPair) {
        if (zipPair == null || zipPair.from == null || zipPair.to == null) {
            return "";
        }
        return "PLZ " + zipPair.from.getZipcode() + " nach PLZ " + zipPair.to.getZipcode();
    }

    private String formatAreaServiceName(Long fromAreaId, Long toAreaId) {
        return formatAreaLabel(fromAreaId) + " nach " + formatAreaLabel(toAreaId);
    }

    private String formatAreaLabel(Long areaId) {
        if (areaId == null) {
            return "";
        }
        ServiceZipCodeArea area = serviceZipCodeAreaRepository.findById(areaId).orElse(null);
        if (area == null) {
            return "Bereich " + areaId;
        }
        String name = area.getName() != null ? area.getName().trim() : "";
        String longname = area.getLongname() != null ? area.getLongname().trim() : "";
        if (name.isBlank()) {
            return longname.isBlank() ? "Bereich " + areaId : longname;
        }
        return longname.isBlank() ? name : name + " (" + longname + ")";
    }

    private PriceComponent resolveServiceComponent(String serviceName,
                                                  String vehicleTypeName,
                                                  Long customerId,
                                                  Long hqId,
                                                  LocalDateTime pricingDateTime) {
        ServiceCatalogReference catalog = resolveServiceCatalogReference(serviceName, vehicleTypeName, customerId, hqId);
        if (catalog == null) {
            return null;
        }
        ServiceHistory customerHistory = loadLatestServiceHistory(catalog, customerId, pricingDateTime);
        ServiceHistory commonHistory = customerId != null && customerId > 0
                ? loadLatestServiceHistory(catalog, 0L, pricingDateTime)
                : null;
        return mergePriceComponents(customerHistory, commonHistory);
    }

    private ServiceHistory loadLatestServiceHistory(ServiceCatalogReference catalog, Long customerId, LocalDateTime pricingDateTime) {
        List<ServiceHistory> rows = serviceHistoryRepository.findLatestPriceForType(
                catalog.serviceId,
                catalog.serviceTypeId,
                customerId != null ? customerId : 0L,
                catalog.headquartersId,
                pricingDateTime
        );
        return rows.isEmpty() ? null : rows.get(0);
    }

    private PriceComponent resolveZipCodeComponent(ZipPair zipPair,
                                                   Long customerId,
                                                   Long hqId,
                                                   LocalDateTime pricingDateTime,
                                                   Integer mappedVehicleTypeId) {
        ServiceZipCodeHistory customerHistory = loadZipCodeHistory(
                zipPair.from.getId(),
                zipPair.to.getId(),
                customerId,
                hqId,
                pricingDateTime,
                mappedVehicleTypeId
        );
        ServiceZipCodeHistory commonHistory = customerId != null && customerId > 0
                ? loadZipCodeHistory(zipPair.from.getId(), zipPair.to.getId(), 0L, hqId, pricingDateTime, mappedVehicleTypeId)
                : null;
        return mergePriceComponents(customerHistory, commonHistory);
    }

    private ServiceZipCodeHistory loadZipCodeHistory(Long fromZipCodeId,
                                                     Long toZipCodeId,
                                                     Long customerId,
                                                     Long hqId,
                                                     LocalDateTime pricingDateTime,
                                                     Integer mappedVehicleTypeId) {
        List<ServiceZipCodeHistory> rows = serviceZipCodeHistoryRepository.findLatest(
                fromZipCodeId,
                toZipCodeId,
                customerId != null ? customerId : 0L,
                hqId,
                mappedVehicleTypeId != null ? mappedVehicleTypeId : 0,
                pricingDateTime
        );
        ServiceZipCodeHistory latest = rows.isEmpty() ? null : rows.get(0);
        if (latest == null || !hasAmount(normalizeLegacyMoney(latest.getPrice()))) {
            List<ServiceZipCodeHistory> fallbackRows = serviceZipCodeHistoryRepository.findLatest(
                    fromZipCodeId,
                    toZipCodeId,
                    customerId != null ? customerId : 0L,
                    hqId,
                    0,
                    pricingDateTime
            );
            if (!fallbackRows.isEmpty()) {
                latest = fallbackRows.get(0);
            }
        }
        return latest;
    }

    private PriceComponent resolveAreaComponent(TourDetailDto from,
                                                TourDetailDto to,
                                                ZipPair zipPair,
                                                Long customerId,
                                                Long hqId,
                                                LocalDateTime pricingDateTime,
                                                Integer mappedVehicleTypeId) {
        Long fromAreaId = resolveAreaId(from, zipPair.from, hqId);
        Long toAreaId = resolveAreaId(to, zipPair.to, hqId);
        if (fromAreaId == null || toAreaId == null) {
            return isInvmodeFallbackEnabled(hqId)
                    ? resolveZipCodeComponent(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId)
                    : null;
        }

        ServiceZipCodeAreaHistory customerHistory = loadAreaHistory(
                fromAreaId,
                toAreaId,
                customerId,
                hqId,
                pricingDateTime,
                mappedVehicleTypeId
        );
        ServiceZipCodeAreaHistory commonHistory = customerId != null && customerId > 0
                ? loadAreaHistory(fromAreaId, toAreaId, 0L, hqId, pricingDateTime, mappedVehicleTypeId)
                : null;
        PriceComponent component = mergePriceComponents(customerHistory, commonHistory);
        if ((component == null || !component.hasAnyPrice()) && isInvmodeFallbackEnabled(hqId)) {
            return resolveZipCodeComponent(zipPair, customerId, hqId, pricingDateTime, mappedVehicleTypeId);
        }
        return component;
    }

    private Long resolveAreaId(TourDetailDto tour, ServiceZipCode zipCode, Long hqId) {
        Long addressAreaId = resolveAreaIdFromAddress(tour, hqId);
        if (addressAreaId != null) {
            return addressAreaId;
        }
        if (zipCode == null || zipCode.getId() == null) {
            return null;
        }
        return serviceZipCodeAreaMappingRepository.findAreaIdByZipCodeIdAndHeadquartersId(zipCode.getId(), hqId).orElse(null);
    }

    private Long resolveAreaIdFromAddress(TourDetailDto tour, Long hqId) {
        if (tour == null || hqId == null) {
            return null;
        }
        String street = normalizeStreet(tour.getStreet());
        String zipcode = normalizeZipcode(tour.getZipcode());
        String originalHouseNumber = normalizeOriginalHouseNumber(tour.getHsno());
        Integer numericHouseNumber = normalizeNumericHouseNumber(tour.getHsno());
        if (street.isBlank() || zipcode.isBlank() || originalHouseNumber.isBlank() || numericHouseNumber == null) {
            return null;
        }

        int lowerRange = 0;
        int upperRange = 99999;
        boolean evenHouseNumber = numericHouseNumber % 2 == 0;
        Long resolvedAreaId = null;

        for (ServiceZipCodeAreaAddress candidate : serviceZipCodeAreaAddressRepository.findAddressCandidates(hqId, zipcode, street)) {
            int candidateFrom = parseInteger(candidate.getHouseNumberFrom(), 0);
            int candidateTo = parseInteger(candidate.getHouseNumberTo(), 99999);
            int mode = candidate.getMode() != null ? candidate.getMode() : 0;
            if ((lowerRange <= candidateFrom && candidateFrom <= numericHouseNumber)
                    && (upperRange >= candidateTo && candidateTo >= numericHouseNumber)
                    && matchesHouseNumberMode(evenHouseNumber, mode)) {
                lowerRange = candidateFrom;
                upperRange = candidateTo;
                resolvedAreaId = candidate.getAreaId();
                Long specialAreaId = resolveSpecialAreaId(candidate.getId(), originalHouseNumber);
                if (specialAreaId != null) {
                    resolvedAreaId = specialAreaId;
                }
            }
        }
        return resolvedAreaId;
    }

    private Long resolveSpecialAreaId(Long areaAddressId, String originalHouseNumber) {
        if (areaAddressId == null || originalHouseNumber == null || originalHouseNumber.isBlank()) {
            return null;
        }
        String key = ParameterKeys.MASK_AREA_ID_FROM_ADDRESS + areaAddressId;
        String configuredValue = parameterService.getParameterValueWithFallback(key, 0L);
        if (configuredValue == null || configuredValue.isBlank()) {
            return null;
        }
        for (String entry : configuredValue.split(";")) {
            String[] pair = entry.split(",");
            if (pair.length != 2) {
                continue;
            }
            if (originalHouseNumber.equals(pair[0].replace(" ", "").trim())) {
                return parseLong(pair[1]);
            }
        }
        return null;
    }

    private boolean matchesHouseNumberMode(boolean evenHouseNumber, int mode) {
        return evenHouseNumber
                ? mode == 0 || mode == 2
                : mode == 0 || mode == 1;
    }

    private boolean isMarkupSuppressedForBasicPrice(Long hqId) {
        if (hqId == null) {
            return false;
        }
        return "1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.MASK_NO_MARKUP_ON_VEHICLE_PRICE, hqId));
    }

    private ServiceZipCodeAreaHistory loadAreaHistory(Long fromAreaId,
                                                      Long toAreaId,
                                                      Long customerId,
                                                      Long hqId,
                                                      LocalDateTime pricingDateTime,
                                                      Integer mappedVehicleTypeId) {
        List<ServiceZipCodeAreaHistory> rows = serviceZipCodeAreaHistoryRepository.findLatest(
                fromAreaId,
                toAreaId,
                customerId != null ? customerId : 0L,
                hqId,
                mappedVehicleTypeId != null ? mappedVehicleTypeId : 0,
                pricingDateTime
        );
        ServiceZipCodeAreaHistory latest = rows.isEmpty() ? null : rows.get(0);
        if (latest == null || !hasAmount(normalizeLegacyMoney(latest.getPrice()))) {
            List<ServiceZipCodeAreaHistory> fallbackRows = serviceZipCodeAreaHistoryRepository.findLatest(
                    fromAreaId,
                    toAreaId,
                    customerId != null ? customerId : 0L,
                    hqId,
                    0,
                    pricingDateTime
            );
            if (!fallbackRows.isEmpty()) {
                latest = fallbackRows.get(0);
            }
        }
        return latest;
    }

    private CourierServicePricing resolveCourierServicePricing(Map<Integer, JobPrice> prices,
                                                              BigDecimal servicePrice,
                                                              BigDecimal courierProvisionRate,
                                                              BigDecimal discountRate,
                                                              boolean servicePriceDiscountApplied,
                                                              Long customerId,
                                                              Long hqId,
                                                              LocalDateTime pricingDateTime,
                                                              String vehicleTypeName) {
        BigDecimal existingRate = money(value(prices, JB_CR_SERVICE_PROV));
        if (hasExplicitCourierServicePrice(prices)) {
            return new CourierServicePricing(resolveExplicitCourierServicePrice(prices), existingRate);
        }
        if (!hasAmount(servicePrice)) {
            return CourierServicePricing.empty();
        }

        BigDecimal serviceRate = existingRate;
        if (!hasAmount(serviceRate)) {
            BigDecimal globalCourierDiscountRate = resolveGlobalCourierDiscountRate(customerId, hqId, pricingDateTime, vehicleTypeName);
            if (hasAmount(globalCourierDiscountRate)) {
                serviceRate = globalCourierDiscountRate;
            } else if (hasAmount(courierProvisionRate)) {
                serviceRate = courierProvisionRate;
            }
        }

        BigDecimal derived = deriveCourierServicePrice(servicePrice, serviceRate, discountRate, servicePriceDiscountApplied);
        return new CourierServicePricing(derived, serviceRate);
    }

    private BigDecimal deriveCourierServicePrice(BigDecimal servicePrice,
                                                 BigDecimal courierProvisionRate,
                                                 BigDecimal discountRate,
                                                 boolean servicePriceDiscountApplied) {
        if (!hasAmount(servicePrice)) {
            return BigDecimal.ZERO;
        }

        BigDecimal derived = hasAmount(courierProvisionRate)
                ? applyDiscount(servicePrice, courierProvisionRate)
                : BigDecimal.ZERO;
        if (servicePriceDiscountApplied) {
            derived = applyDiscount(derived, discountRate);
        }
        return derived;
    }

    private CourierSubtotalPricing applyRouteCourierProvisionRate(RoutePricing routePricing,
                                                                  BigDecimal courierProvisionRate,
                                                                  BigDecimal courierMarkup) {
        BigDecimal eligibleSubtotal = applyDiscount(routePricing.courierMarkupEligibleCustomerSubtotal(), courierProvisionRate);
        BigDecimal exemptSubtotal = applyDiscount(routePricing.courierMarkupExemptCustomerSubtotal(), courierProvisionRate);
        BigDecimal subtotal = eligibleSubtotal.add(exemptSubtotal);
        BigDecimal total = applyMarkup(eligibleSubtotal, courierMarkup).add(exemptSubtotal);
        return new CourierSubtotalPricing(scale(subtotal), scale(total));
    }

    private BigDecimal resolveGlobalCourierDiscountRate(Long customerId,
                                                        Long hqId,
                                                        LocalDateTime pricingDateTime,
                                                        String vehicleTypeName) {
        PriceComponent component = resolveServiceComponent(
                GLOBAL_COURIER_DISCOUNT_SERVICE,
                vehicleTypeName,
                customerId,
                hqId,
                pricingDateTime
        );
        return component != null ? money(component.courierPrice) : BigDecimal.ZERO;
    }

    private BigDecimal resolveGlobalCourierPriceRate(Long customerId,
                                                     Long hqId,
                                                     LocalDateTime pricingDateTime,
                                                     String vehicleTypeName) {
        PriceComponent component = resolveServiceComponent(
                GLOBAL_COURIER_PRICE_RATE_SERVICE,
                vehicleTypeName,
                customerId,
                hqId,
                pricingDateTime
        );
        return component != null ? money(component.courierPrice) : BigDecimal.ZERO;
    }

    private BigDecimal resolveFuelMarkup(Long customerId,
                                         Customer customer,
                                         Long hqId,
                                         LocalDateTime pricingDateTime,
                                         String vehicleTypeName) {
        if (!isFuelMarkupEnabled(customerId, customer)) {
            return BigDecimal.ZERO;
        }
        BigDecimal configuredMarkup = normalizeLegacyMoney(customer != null ? customer.getFuelMarkupProvision() : null);
        if (hasAmount(configuredMarkup)) {
            BigDecimal historicalMarkup = loadLatestCustomerFieldPrice(customerId, CUSTOMER_FUEL_MARKUP_FIELD, pricingDateTime);
            return historicalMarkup != null ? historicalMarkup : configuredMarkup;
        }
        if (hqId == null || vehicleTypeName.isBlank()) {
            return BigDecimal.ZERO;
        }
        PriceComponent component = resolveServiceComponent(
                FUEL_MARKUP_SERVICE,
                vehicleTypeName,
                customerId,
                hqId,
                pricingDateTime
        );
        if (component == null) {
            return BigDecimal.ZERO;
        }
        return applyDiscount(component.price, component.discount);
    }

    private BigDecimal resolveCourierFuelMarkup(Long customerId,
                                                Customer customer,
                                                Long hqId,
                                                LocalDateTime pricingDateTime,
                                                String vehicleTypeName) {
        if (hqId != null && !vehicleTypeName.isBlank()) {
            PriceComponent component = resolveServiceComponent(
                    FUEL_MARKUP_SERVICE,
                    vehicleTypeName,
                    customerId,
                    hqId,
                    pricingDateTime
            );
            if (component != null) {
                BigDecimal courierMarkup = normalizeLegacyMoney(component.courierPrice);
                if (hasAmount(courierMarkup)) {
                    return courierMarkup;
                }
            }
        }
        return resolveFuelMarkup(customerId, customer, hqId, pricingDateTime, vehicleTypeName);
    }

    private BigDecimal loadLatestCustomerFieldPrice(Long customerId, String fieldName, LocalDateTime pricingDateTime) {
        if (customerId == null || customerId <= 0 || fieldName == null || fieldName.isBlank() || pricingDateTime == null) {
            return null;
        }
        List<ServicePriceHistory> rows = servicePriceHistoryRepository.findLatestFieldValue(
                CUSTOMER_OBJECT_TYPE,
                customerId,
                fieldName,
                pricingDateTime
        );
        if (rows.isEmpty()) {
            return null;
        }
        return normalizeLegacyMoney(rows.get(0).getPrice());
    }

    private BigDecimal resolveCourierAgio(Map<Integer, JobPrice> prices,
                                          Job job,
                                          Long customerId,
                                          Long hqId,
                                          LocalDateTime pricingDateTime) {
        if (value(prices, JB_CR_AGIO) != null) {
            return money(value(prices, JB_CR_AGIO));
        }
        if (!isCourierPriceModeActive(hqId, pricingDateTime) || job == null || customerId == null || customerId <= 0) {
            return BigDecimal.ZERO;
        }
        if (job.getCourierId() == null || job.getCourierId() <= 0 || pricingDateTime == null) {
            return BigDecimal.ZERO;
        }
        List<ServicePriceHistory> rows = servicePriceHistoryRepository.findLatestFieldValueWithContext(
                COURIER_OBJECT_TYPE,
                job.getCourierId(),
                COURIER_CUSTOMER_AGIO_FIELD,
                customerId.toString(),
                pricingDateTime
        );
        if (rows.isEmpty()) {
            return BigDecimal.ZERO;
        }
        return normalizeLegacyMoney(rows.get(0).getPrice());
    }

    private boolean isCourierPriceModeActive(Long hqId, LocalDateTime pricingDateTime) {
        if (hqId == null || pricingDateTime == null) {
            return false;
        }
        if (!isCourierPriceModeEnabled(hqId)) {
            return false;
        }
        String activeFromValue = parameterService.getParameterValueWithFallback(ParameterKeys.MASK_CR_PRICE_MODE_DATE, hqId);
        if (activeFromValue == null || activeFromValue.isBlank()) {
            return true;
        }
        LocalDate activeFromDate = parseDate(activeFromValue);
        return activeFromDate == null || !pricingDateTime.toLocalDate().isBefore(activeFromDate);
    }

    private LocalDate parseDate(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return LocalDate.parse(value.trim());
        } catch (RuntimeException ignored) {
            try {
                return LocalDateTime.parse(value.trim()).toLocalDate();
            } catch (RuntimeException ignoredAgain) {
                return null;
            }
        }
    }

    private boolean isCourierPriceModeEnabled(Long hqId) {
        if (hqId == null) {
            return false;
        }
        return "1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.MASK_CR_PRICE_MODE, hqId));
    }

    private ServiceCatalogReference resolveServiceCatalogReference(String serviceName,
                                                                  String vehicleTypeName,
                                                                  Long customerId,
                                                                  Long hqId) {
        if (hqId == null || serviceName == null || serviceName.isBlank() || vehicleTypeName == null || vehicleTypeName.isBlank()) {
            return null;
        }

        ServiceType serviceType = findServiceTypeByName(vehicleTypeName, hqId, customerId);
        Long effectiveHqId = hqId;
        if (serviceType == null) {
            serviceType = findServiceTypeByName(vehicleTypeName, 0L, customerId);
            effectiveHqId = 0L;
        }
        if (serviceType == null) {
            return null;
        }

        ServiceEntity service = findServiceByName(serviceName, effectiveHqId, customerId);
        if (service == null) {
            return null;
        }
        return new ServiceCatalogReference(service.getId(), serviceType.getId(), effectiveHqId);
    }

    private ServiceType findServiceTypeByName(String name, Long hqId, Long customerId) {
        return serviceTypeRepository.findAvailableForCustomer(hqId, customerId != null ? customerId : 0L).stream()
                .filter(type -> type.getMode() != null && type.getMode() == 0)
                .filter(type -> name.equals(type.getName()))
                .max(Comparator
                        .comparing((ServiceType type) -> customerMatch(type.getCustomerId(), customerId))
                        .thenComparing(type -> headquartersMatch(type.getHeadquartersId(), hqId)))
                .orElse(null);
    }

    private ServiceEntity findServiceByName(String name, Long hqId, Long customerId) {
        return serviceEntityRepository.findAvailableForCustomer(hqId, customerId != null ? customerId : 0L).stream()
                .filter(service -> service.getMode() != null && service.getMode() == 0)
                .filter(service -> name.equals(service.getName()))
                .max(Comparator
                        .comparing((ServiceEntity service) -> customerMatch(service.getCustomerId(), customerId))
                        .thenComparing(service -> headquartersMatch(service.getHeadquartersId(), hqId)))
                .orElse(null);
    }

    private int customerMatch(Long scopedCustomerId, Long requestedCustomerId) {
        return requestedCustomerId != null && requestedCustomerId > 0 && requestedCustomerId.equals(scopedCustomerId) ? 1 : 0;
    }

    private int headquartersMatch(Long scopedHqId, Long requestedHqId) {
        return requestedHqId != null && requestedHqId.equals(scopedHqId) ? 1 : 0;
    }

    private PriceComponent mergePriceComponents(ServiceHistory customerHistory, ServiceHistory commonHistory) {
        return new PriceComponent(
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getPrice() : null, commonHistory != null ? commonHistory.getPrice() : null),
                chooseDirectValue(customerHistory != null ? customerHistory.getCourierPrice() : null, commonHistory != null ? commonHistory.getCourierPrice() : null),
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getDiscount() : null, commonHistory != null ? commonHistory.getDiscount() : null)
        );
    }

    private PriceComponent mergePriceComponents(ServiceZipCodeHistory customerHistory, ServiceZipCodeHistory commonHistory) {
        return new PriceComponent(
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getPrice() : null, commonHistory != null ? commonHistory.getPrice() : null),
                chooseDirectValue(customerHistory != null ? customerHistory.getCourierPrice() : null, commonHistory != null ? commonHistory.getCourierPrice() : null),
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getDiscount() : null, commonHistory != null ? commonHistory.getDiscount() : null)
        );
    }

    private PriceComponent mergePriceComponents(ServiceZipCodeAreaHistory customerHistory, ServiceZipCodeAreaHistory commonHistory) {
        return new PriceComponent(
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getPrice() : null, commonHistory != null ? commonHistory.getPrice() : null),
                chooseDirectValue(customerHistory != null ? customerHistory.getCourierPrice() : null, commonHistory != null ? commonHistory.getCourierPrice() : null),
                mergeLegacyCorrectedValue(customerHistory != null ? customerHistory.getDiscount() : null, commonHistory != null ? commonHistory.getDiscount() : null)
        );
    }

    private BigDecimal chooseDirectValue(BigDecimal primary, BigDecimal fallback) {
        BigDecimal normalizedPrimary = normalizeLegacyMoney(primary);
        if (hasAmount(normalizedPrimary)) {
            return normalizedPrimary;
        }
        BigDecimal normalizedFallback = normalizeLegacyMoney(fallback);
        return hasAmount(normalizedFallback) ? normalizedFallback : normalizedPrimary;
    }

    private BigDecimal mergeLegacyCorrectedValue(BigDecimal customerSpecific, BigDecimal commonValue) {
        BigDecimal normalizedCustomerSpecific = normalizeLegacyMoney(customerSpecific);
        BigDecimal normalizedCommonValue = normalizeLegacyMoney(commonValue);
        if (normalizedCustomerSpecific.compareTo(BigDecimal.ZERO) > 0) {
            return normalizedCustomerSpecific;
        }
        if (normalizedCustomerSpecific.compareTo(BigDecimal.ZERO) < 0) {
            return normalizedCommonValue.add(normalizedCustomerSpecific);
        }
        return normalizedCommonValue;
    }

    private BigDecimal normalizeLegacyMoney(BigDecimal value) {
        BigDecimal normalized = money(value);
        if (PLACEHOLDER_ZERO.compareTo(normalized.setScale(2, RoundingMode.HALF_UP)) == 0) {
            return BigDecimal.ZERO;
        }
        return normalized;
    }

    private ZipPair resolveZipPair(TourDetailDto from, TourDetailDto to) {
        if (from == null || to == null) {
            return null;
        }
        ServiceZipCode fromZipCode = resolveServiceZipCode(from.getZipcode());
        ServiceZipCode toZipCode = resolveServiceZipCode(to.getZipcode());
        if (fromZipCode == null || toZipCode == null) {
            return null;
        }
        return new ZipPair(fromZipCode, toZipCode);
    }

    private ServiceZipCode resolveServiceZipCode(String zipcode) {
        String normalized = normalizeZipcode(zipcode);
        if (normalized.isBlank()) {
            return null;
        }
        return serviceZipCodeRepository.findByZipcode(normalized).orElse(null);
    }

    private List<TourDetailDto> normalizeTours(List<TourDetailDto> tours) {
        if (tours == null || tours.isEmpty()) {
            return List.of();
        }
        List<TourDetailDto> normalized = new ArrayList<>();
        for (TourDetailDto tour : tours) {
            if (tour == null || normalizeZipcode(tour.getZipcode()).isBlank()) {
                continue;
            }
            normalized.add(tour);
        }
        normalized.sort(Comparator.comparing(TourDetailDto::getSort, Comparator.nullsLast(Integer::compareTo)));
        return normalized;
    }

    private String normalizeZipcode(String zipcode) {
        return zipcode != null ? zipcode.replace(" ", "").trim() : "";
    }

    private String normalizeStreet(String street) {
        return street != null ? street.trim() : "";
    }

    private String normalizeOriginalHouseNumber(String houseNumber) {
        return houseNumber != null ? houseNumber.replace(" ", "").trim() : "";
    }

    private Integer normalizeNumericHouseNumber(String houseNumber) {
        String normalized = normalizeOriginalHouseNumber(houseNumber);
        if (normalized.isBlank()) {
            return null;
        }
        normalized = normalized.replaceAll("[^\\d]+", "-");
        int separatorIndex = normalized.indexOf('-');
        if (separatorIndex >= 0) {
            normalized = normalized.substring(0, separatorIndex);
        }
        return parseInteger(normalized);
    }

    private int resolveInvoiceMode(Job job, Long customerId, Long hqId) {
        if (hqId == null) {
            return 0;
        }
        Long payerCostCenterId = firstNonNull(job.getCostCenterPayerId(), job.getCostCenterRelatedId());
        if (payerCostCenterId != null) {
            String configuredValue = parameterService.getParameterValueWithFallback(ParameterKeys.COSTCENTER_INV_MODE + payerCostCenterId, hqId);
            Integer configuredMode = parseInteger(configuredValue);
            if (configuredMode != null) {
                return configuredMode;
            }
        }
        if (customerId != null) {
            Customer customer = customerRepository.findById(customerId).orElse(null);
            if (customer != null && customer.getInvoiceMode() != null) {
                return customer.getInvoiceMode();
            }
        }
        Headquarters headquarters = headquartersRepository.findById(hqId).orElse(null);
        return headquarters != null && headquarters.getInvoiceMode() != null ? headquarters.getInvoiceMode() : 0;
    }

    private Integer parseInteger(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return Integer.parseInt(value.trim());
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private int parseInteger(String value, int defaultValue) {
        Integer parsedValue = parseInteger(value);
        return parsedValue != null ? parsedValue : defaultValue;
    }

    private Long parseLong(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return Long.parseLong(value.trim());
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private boolean isInvmodeFallbackEnabled(Long hqId) {
        if (hqId == null) {
            return false;
        }
        return "1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.MASK_INVMODE_FALLBACK, hqId));
    }

    private Integer resolveMappedVehicleTypeId(Integer vehicleTypeId, Long hqId) {
        Integer effectiveVehicleTypeId = vehicleTypeId != null ? vehicleTypeId : 0;
        if (hqId == null || effectiveVehicleTypeId == 0) {
            return effectiveVehicleTypeId;
        }
        if (!"1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.SERVICE_VEHICLE_TYPE_ENABLED, hqId))) {
            return effectiveVehicleTypeId;
        }
        String mapping = parameterService.getParameterValueWithFallback(ParameterKeys.SERVICE_VEHICLE_TYPE_MAPPING, hqId);
        if (mapping == null || mapping.isBlank()) {
            return effectiveVehicleTypeId;
        }
        for (String entry : mapping.split("\\|")) {
            String[] parts = entry.split("=");
            if (parts.length != 2) {
                continue;
            }
            Integer source = parseInteger(parts[0]);
            Integer target = parseInteger(parts[1]);
            if (source != null && target != null && source.equals(effectiveVehicleTypeId)
                    && metaTypeRepository.findBySortAndType(target, VEHICLE_TYPE_META).isPresent()) {
                return target;
            }
        }
        return effectiveVehicleTypeId;
    }

    private String resolveVehicleTypeName(Integer vehicleTypeId) {
        if (vehicleTypeId == null || vehicleTypeId == 0) {
            return "";
        }
        return metaTypeRepository.findBySortAndType(vehicleTypeId, VEHICLE_TYPE_META)
                .map(MetaType::getValue)
                .orElse("");
    }

    private Customer resolveCustomer(Job job) {
        Long customerId = resolveCustomerId(job);
        if (customerId == null) {
            return null;
        }
        return customerRepository.findById(customerId).orElse(null);
    }

    private Long resolveCustomerId(Job job) {
        Long payerCostCenterId = firstNonNull(job.getCostCenterPayerId(), job.getCostCenterRelatedId());
        if (payerCostCenterId == null) {
            return null;
        }
        return costCenterRepository.findById(payerCostCenterId)
                .map(CostCenter::getCustomerId)
                .orElse(null);
    }

    private BigDecimal resolveDiscountRate(Job job, Customer customer) {
        BigDecimal defaultDiscount = customer != null ? money(customer.getDiscount()) : BigDecimal.ZERO;
        BigDecimal fixDiscount = customer != null ? money(customer.getFixpriceDiscount()) : BigDecimal.ZERO;
        if (hasAmount(job.getFixPrice()) && hasAmount(fixDiscount)) {
            return fixDiscount;
        }
        return defaultDiscount;
    }

    private BigDecimal resolveSelfServiceDiscount(Map<Integer, JobPrice> prices, Long hqId) {
        BigDecimal existingDiscount = money(value(prices, JB_SELF_SERVICE_DISCOUNT));
        if (hasAmount(existingDiscount)) {
            return existingDiscount;
        }
        if (hqId == null) {
            return BigDecimal.ZERO;
        }
        return "1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.MASK_CS_SELF_SERVICE_DISCOUNT, hqId))
                ? BigDecimal.ZERO
                : BigDecimal.ZERO;
    }

    private boolean isServicePriceDiscountApplied(Long hqId) {
        if (hqId == null) {
            return false;
        }
        return "1".equals(parameterService.getParameterValueWithFallback(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, hqId));
    }

    private BigDecimal resolveAutomaticStreetServicePrice(Job job,
                                                          List<TourDetailDto> tours,
                                                          LocalDateTime pricingDateTime) {
        if (job == null) {
            return BigDecimal.ZERO;
        }
        if (job.getId() != null) {
            return loadStoredAutomaticStreetServicePrice(job.getId());
        }
        return resolveCalculatedAutomaticStreetServicePrice(job, tours, pricingDateTime);
    }

    private BigDecimal resolveCalculatedAutomaticStreetServicePrice(Job job, List<TourDetailDto> tours) {
        return resolveCalculatedAutomaticStreetServicePrice(
                job,
                tours,
                job != null && job.getOrderTime() != null ? job.getOrderTime() : LocalDateTime.now()
        );
    }

    private BigDecimal resolveCalculatedAutomaticStreetServicePrice(Job job,
                                                                    List<TourDetailDto> tours,
                                                                    LocalDateTime pricingDateTime) {
        if (job == null || job.getHeadquartersId() == null || pricingDateTime == null) {
            return BigDecimal.ZERO;
        }
        AutomaticStreetServicePriceRule rule = parseAutomaticStreetServicePriceRule(job.getHeadquartersId());
        if (rule == null || !rule.isActiveFor(pricingDateTime.toLocalDate())) {
            return BigDecimal.ZERO;
        }
        BigDecimal total = BigDecimal.ZERO;
        for (TourDetailDto tour : tours != null ? tours : List.<TourDetailDto>of()) {
            String zipcode = normalizeZipcode(tour != null ? tour.getZipcode() : null);
            String street = normalizeStreet(tour != null ? tour.getStreet() : null);
            Integer houseNumber = normalizeNumericHouseNumber(tour != null ? tour.getHsno() : null);
            if (rule.matches(zipcode, street, houseNumber)) {
                total = total.add(rule.price());
            }
        }
        return scale(total);
    }

    private AutomaticStreetServicePriceRule parseAutomaticStreetServicePriceRule(Long hqId) {
        if (hqId == null) {
            return null;
        }
        String configuredValue = parameterService.getParameterValueWithFallback(ParameterKeys.MASK_SERVICE_PRICE_STREET, hqId);
        if (configuredValue == null || configuredValue.isBlank()) {
            return null;
        }
        String[] parts = configuredValue.split(",");
        if (parts.length < 7) {
            return null;
        }
        Integer oddFrom = parseRangeBound(parts[2], 0);
        Integer oddTo = parseRangeBound(parts[2], 1);
        Integer evenFrom = parseRangeBound(parts[3], 0);
        Integer evenTo = parseRangeBound(parts[3], 1);
        LocalDate validFrom = parseDate(parts[4]);
        LocalDate validTo = parseDate(parts[5]);
        BigDecimal price = parseMoney(parts[6]);
        if (oddFrom == null || oddTo == null || evenFrom == null || evenTo == null || validFrom == null || validTo == null || !hasAmount(price)) {
            return null;
        }
        return new AutomaticStreetServicePriceRule(
                normalizeZipcode(parts[0]),
                normalizeStreet(parts[1]),
                oddFrom,
                oddTo,
                evenFrom,
                evenTo,
                validFrom,
                validTo,
                scale(price)
        );
    }

    private BigDecimal loadStoredAutomaticStreetServicePrice(Long jobId) {
        if (jobId == null || genericDataContainerRepository == null) {
            return BigDecimal.ZERO;
        }
        return genericDataContainerRepository.findByTypeAndIdAndField(
                        JOB_OBJECT_TYPE,
                        jobId,
                        AUTOMATIC_STREET_SERVICE_PRICE_FIELD
                )
                .map(entry -> parseMoney(entry.getContent()))
                .map(this::amountOrZero)
                .orElse(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
    }

    private BigDecimal resolveBaseServicePrice(Job job, BigDecimal automaticStreetServicePrice) {
        BigDecimal totalServicePrice = money(job != null ? job.getServicePrice() : null);
        if (job == null || job.getId() == null || !hasAmount(automaticStreetServicePrice)) {
            return totalServicePrice;
        }
        BigDecimal baseServicePrice = totalServicePrice.subtract(amountOrZero(automaticStreetServicePrice));
        return baseServicePrice.signum() < 0 ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : scale(baseServicePrice);
    }

    private BigDecimal resolveOrderMarkup(Job job,
                                          Map<Integer, JobPrice> prices,
                                          Customer customer,
                                          Long customerId,
                                          Long hqId,
                                          LocalDateTime pricingDateTime,
                                          String vehicleTypeName) {
        if (job.getMarkup() != null) {
            return money(job.getMarkup());
        }
        if (value(prices, JB_MARKUP_MAN) != null) {
            return money(value(prices, JB_MARKUP_MAN));
        }
        return resolveFuelMarkup(customerId, customer, hqId, pricingDateTime, vehicleTypeName);
    }

    private BigDecimal resolveCourierMarkup(Map<Integer, JobPrice> prices,
                                            Customer customer,
                                            Long customerId,
                                            Long hqId,
                                            LocalDateTime pricingDateTime,
                                            String vehicleTypeName) {
        if (value(prices, JB_CR_MARKUP_MAN) != null) {
            return money(value(prices, JB_CR_MARKUP_MAN));
        }
        if (value(prices, JB_CR_MARKUP) != null) {
            return money(value(prices, JB_CR_MARKUP));
        }
        return resolveCourierFuelMarkup(customerId, customer, hqId, pricingDateTime, vehicleTypeName);
    }

    private BigDecimal resolveCourierSubtotal(Job job, Map<Integer, JobPrice> prices) {
        if (job.getCourierSubprice() != null) {
            return money(job.getCourierSubprice());
        }
        if (value(prices, JB_CR_SUBTOTAL) != null) {
            return money(value(prices, JB_CR_SUBTOTAL));
        }
        BigDecimal courierMarkup = value(prices, JB_CR_MARKUP_MAN) != null
                ? money(value(prices, JB_CR_MARKUP_MAN))
                : money(value(prices, JB_CR_MARKUP));
        BigDecimal courierServicePrice = resolveExplicitCourierServicePrice(prices);
        if (!hasAmount(job.getCourierPrice())) {
            return BigDecimal.ZERO;
        }
        BigDecimal totalWithoutService = money(job.getCourierPrice()).subtract(courierServicePrice);
        if (totalWithoutService.signum() <= 0) {
            return BigDecimal.ZERO;
        }
        return inverseMarkup(totalWithoutService, courierMarkup);
    }

    private boolean hasExplicitCourierSubtotal(Job job, Map<Integer, JobPrice> prices) {
        return job.getCourierSubprice() != null || value(prices, JB_CR_SUBTOTAL) != null;
    }

    private BigDecimal resolveExplicitCourierFixPrice(Map<Integer, JobPrice> prices) {
        return money(value(prices, JB_CR_FIXPRICE));
    }

    private BigDecimal resolveManualCourierTotalSubtotal(Job job,
                                                         BigDecimal courierMarkup,
                                                         BigDecimal courierAgio,
                                                         BigDecimal courierServicePrice,
                                                         boolean includeCourierServiceInCourierTotals) {
        if (job == null || !hasAmount(job.getCourierPrice())) {
            return BigDecimal.ZERO;
        }
        BigDecimal totalWithoutService = money(job.getCourierPrice());
        if (includeCourierServiceInCourierTotals) {
            totalWithoutService = totalWithoutService.subtract(money(courierServicePrice));
        }
        if (totalWithoutService.signum() <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal subtotal = inverseMarkup(totalWithoutService, courierMarkup);
        if (hasAmount(courierAgio)) {
            subtotal = inverseMarkup(subtotal, courierAgio);
        }
        return scale(subtotal);
    }

    private Integer resolveCourierServicePriceMetaSort(Map<Integer, JobPrice> prices) {
        if (prices.containsKey(JB_CR_FIXSERVICEPRICE)) {
            return JB_CR_FIXSERVICEPRICE;
        }
        return JB_CR_SERVICEPRICE;
    }

    private boolean hasExplicitCourierServicePrice(Map<Integer, JobPrice> prices) {
        return value(prices, JB_CR_FIXSERVICEPRICE) != null || value(prices, JB_CR_SERVICEPRICE) != null;
    }

    private boolean hasManualCourierServicePrice(Map<Integer, JobPrice> prices) {
        return value(prices, JB_CR_FIXSERVICEPRICE) != null;
    }

    private BigDecimal resolveExplicitCourierServicePrice(Map<Integer, JobPrice> prices) {
        if (value(prices, JB_CR_FIXSERVICEPRICE) != null) {
            return money(value(prices, JB_CR_FIXSERVICEPRICE));
        }
        return money(value(prices, JB_CR_SERVICEPRICE));
    }

    private BigDecimal servicePriceContribution(BigDecimal servicePrice,
                                                BigDecimal discountRate,
                                                boolean servicePriceDiscountApplied) {
        if (!hasAmount(servicePrice)) {
            return BigDecimal.ZERO;
        }
        return servicePriceDiscountApplied ? applyDiscount(servicePrice, discountRate) : servicePrice;
    }

    private BigDecimal amountOrZero(BigDecimal value) {
        return value != null ? value.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }

    private Integer parseRangeBound(String range, int index) {
        if (range == null || range.isBlank()) {
            return null;
        }
        String[] bounds = range.trim().split("-");
        if (bounds.length != 2 || index < 0 || index >= bounds.length) {
            return null;
        }
        return parseInteger(bounds[index].trim());
    }

    private BigDecimal parseMoney(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return new BigDecimal(value.trim().replace(",", ".")).setScale(2, RoundingMode.HALF_UP);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private Map<Integer, JobPrice> toPriceMap(List<JobPrice> inputPrices) {
        Map<Integer, JobPrice> prices = new LinkedHashMap<>();
        if (inputPrices == null) {
            return prices;
        }
        for (JobPrice inputPrice : inputPrices) {
            if (inputPrice == null || inputPrice.getMetaSort() == null) {
                continue;
            }
            JobPrice copy = new JobPrice();
            copy.setJobId(inputPrice.getJobId());
            copy.setMetaSort(inputPrice.getMetaSort());
            copy.setPrice(scale(money(inputPrice.getPrice())));
            copy.setRemark(inputPrice.getRemark());
            copy.setReference(inputPrice.getReference());
            prices.put(copy.getMetaSort(), copy);
        }
        return prices;
    }

    private void syncDerivedPrice(Map<Integer, JobPrice> prices,
                                  int metaSort,
                                  BigDecimal price,
                                  String remark,
                                  Long reference) {
        if (!hasAmount(price) && !prices.containsKey(metaSort)) {
            return;
        }
        if (!hasAmount(price)) {
            prices.remove(metaSort);
            return;
        }
        JobPrice row = prices.computeIfAbsent(metaSort, key -> new JobPrice());
        row.setMetaSort(metaSort);
        row.setPrice(scale(price));
        row.setRemark(remark);
        row.setReference(reference);
    }

    private String existingRemark(Map<Integer, JobPrice> prices, int metaSort) {
        JobPrice row = prices.get(metaSort);
        return row != null ? row.getRemark() : null;
    }

    private Long existingReference(Map<Integer, JobPrice> prices, int metaSort) {
        JobPrice row = prices.get(metaSort);
        return row != null ? row.getReference() : null;
    }

    private BigDecimal value(Map<Integer, JobPrice> prices, int metaSort) {
        JobPrice row = prices.get(metaSort);
        return row != null ? row.getPrice() : null;
    }

    private BigDecimal percentDifference(BigDecimal source, BigDecimal target) {
        if (!hasAmount(source) || source.signum() == 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal fraction = source.subtract(target).divide(source, 6, RoundingMode.HALF_UP);
        return scale(fraction.multiply(ONE_HUNDRED));
    }

    private BigDecimal applyDiscount(BigDecimal amount, BigDecimal discountRate) {
        if (!hasAmount(amount)) {
            return BigDecimal.ZERO;
        }
        if (!hasAmount(discountRate)) {
            return scale(amount);
        }
        BigDecimal factor = ONE_HUNDRED.subtract(discountRate).divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP);
        return scale(amount.multiply(factor));
    }

    private BigDecimal applyMarkup(BigDecimal amount, BigDecimal markup) {
        if (!hasAmount(amount)) {
            return BigDecimal.ZERO;
        }
        if (!hasAmount(markup)) {
            return scale(amount);
        }
        BigDecimal factor = ONE_HUNDRED.add(markup).divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP);
        return scale(amount.multiply(factor));
    }

    private BigDecimal inverseMarkup(BigDecimal amount, BigDecimal markup) {
        if (!hasAmount(amount)) {
            return BigDecimal.ZERO;
        }
        if (!hasAmount(markup)) {
            return scale(amount);
        }
        BigDecimal divisor = ONE_HUNDRED.add(markup).divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP);
        return scale(amount.divide(divisor, 6, RoundingMode.HALF_UP));
    }

    private BigDecimal scale(BigDecimal value) {
        return money(value).setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal money(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }

    private boolean hasAmount(BigDecimal value) {
        return value != null && value.compareTo(BigDecimal.ZERO) != 0;
    }

    private boolean isFuelMarkupEnabled(Long customerId, Customer customer) {
        if (customerId == null || customerId <= 0) {
            return true;
        }
        return customer != null && Integer.valueOf(1).equals(customer.getFuelMarkupEnabled());
    }

    private <T> T firstNonNull(T first, T second) {
        return first != null ? first : second;
    }

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

    private record ServiceCatalogReference(Long serviceId, Long serviceTypeId, Long headquartersId) { }

    private record PriceComponent(BigDecimal price, BigDecimal courierPrice, BigDecimal discount) {
        private boolean hasAnyPrice() {
            return (price != null && price.compareTo(BigDecimal.ZERO) != 0)
                    || (courierPrice != null && courierPrice.compareTo(BigDecimal.ZERO) != 0);
        }
    }

    private record PriceTotals(BigDecimal customerSubtotal,
                               BigDecimal customerTotal,
                               BigDecimal courierSubtotal,
                               BigDecimal courierTotal) {
        private static PriceTotals empty() {
            return new PriceTotals(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
        }
    }

    private record CourierServicePricing(BigDecimal price, BigDecimal rate) {
        private static CourierServicePricing empty() {
            return new CourierServicePricing(BigDecimal.ZERO, BigDecimal.ZERO);
        }
    }

    private record CourierSubtotalPricing(BigDecimal subtotal, BigDecimal total) { }

    private record RoutePricing(BigDecimal customerSubtotal,
                                BigDecimal customerTotal,
                                BigDecimal courierSubtotal,
                                BigDecimal courierTotal,
                                BigDecimal courierMarkupEligibleCustomerSubtotal,
                                BigDecimal courierMarkupExemptCustomerSubtotal) {
        private static RoutePricing empty() {
            return new RoutePricing(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
        }

        private boolean hasAnyPrice() {
            return (customerSubtotal != null && customerSubtotal.compareTo(BigDecimal.ZERO) != 0)
                    || (customerTotal != null && customerTotal.compareTo(BigDecimal.ZERO) != 0)
                    || (courierSubtotal != null && courierSubtotal.compareTo(BigDecimal.ZERO) != 0)
                    || (courierTotal != null && courierTotal.compareTo(BigDecimal.ZERO) != 0);
        }
    }

    private record ZipPair(ServiceZipCode from, ServiceZipCode to) { }

    private record PersistedRouteSegment(PriceComponent component, String serviceName) { }

    private record AutomaticStreetServicePriceRule(String zipcode,
                                                   String streetPrefix,
                                                   int oddFrom,
                                                   int oddTo,
                                                   int evenFrom,
                                                   int evenTo,
                                                   LocalDate validFrom,
                                                   LocalDate validTo,
                                                   BigDecimal price) {
        private boolean isActiveFor(LocalDate pricingDate) {
            return pricingDate != null
                    && !pricingDate.isBefore(validFrom)
                    && !pricingDate.isAfter(validTo);
        }

        private boolean matches(String normalizedZipcode, String normalizedStreet, Integer houseNumber) {
            if (normalizedZipcode == null || normalizedStreet == null || houseNumber == null) {
                return false;
            }
            return zipcode.equals(normalizedZipcode)
                    && normalizedStreet.startsWith(streetPrefix)
                    && ((evenFrom <= houseNumber && houseNumber <= evenTo)
                    || (oddFrom <= houseNumber && houseNumber <= oddTo));
        }
    }
}
