package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.dto.CourierDetailDto;
import de.votian.services.dto.CourierListItemDto;
import de.votian.services.dto.CustomerDetailDto;
import de.votian.services.dto.CustomerListItemDto;
import de.votian.services.dto.EmployeeDetailDto;
import de.votian.services.dto.EmployeeListItemDto;
import de.votian.services.dto.HeadquartersDetailDto;
import de.votian.services.dto.InvoiceCustomerSummaryDto;
import de.votian.services.dto.InvoiceJobRowDto;
import de.votian.services.dto.InvoiceSummaryDto;
import de.votian.services.dto.JobTourServiceRowDto;
import de.votian.services.dto.JobListRowDto;
import de.votian.services.dto.ServicePriceRowDto;
import de.votian.services.dto.StatisticsSummaryDto;
import de.votian.services.dto.TourDetailDto;
import de.votian.services.entity.Company;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Courier;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Employee;
import de.votian.services.entity.Headquarters;
import de.votian.services.entity.Job;
import de.votian.services.entity.Parameter;
import de.votian.services.entity.ServiceEntity;
import de.votian.services.entity.ServiceHistory;
import de.votian.services.entity.ServiceType;
import de.votian.services.entity.Tour;
import de.votian.services.entity.User;
import de.votian.services.repository.AddressRepository;
import de.votian.services.repository.CompanyRepository;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.CourierRepository;
import de.votian.services.repository.CustomerRepository;
import de.votian.services.repository.EmployeeRepository;
import de.votian.services.repository.HeadquartersRepository;
import de.votian.services.repository.JobRepository;
import de.votian.services.repository.ParameterRepository;
import de.votian.services.repository.ServiceEntityRepository;
import de.votian.services.repository.ServiceHistoryRepository;
import de.votian.services.repository.ServiceTypeRepository;
import de.votian.services.repository.TourRepository;
import de.votian.services.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

@Service
@Transactional(readOnly = true)
public class ViewDataService {

    private final CustomerRepository customerRepository;
    private final CostCenterRepository costCenterRepository;
    private final CourierRepository courierRepository;
    private final EmployeeRepository employeeRepository;
    private final CompanyRepository companyRepository;
    private final UserRepository userRepository;
    private final AddressRepository addressRepository;
    private final TourRepository tourRepository;
    private final HeadquartersRepository headquartersRepository;
    private final ParameterRepository parameterRepository;
    private final JobRepository jobRepository;
    private final JobTourServiceReadService jobTourServiceReadService;
    private final ServiceEntityRepository serviceEntityRepository;
    private final ServiceTypeRepository serviceTypeRepository;
    private final ServiceHistoryRepository serviceHistoryRepository;

    public ViewDataService(CustomerRepository customerRepository,
                           CostCenterRepository costCenterRepository,
                           CourierRepository courierRepository,
                           EmployeeRepository employeeRepository,
                           CompanyRepository companyRepository,
                           UserRepository userRepository,
                           AddressRepository addressRepository,
                           TourRepository tourRepository,
                           HeadquartersRepository headquartersRepository,
                           ParameterRepository parameterRepository,
                           JobRepository jobRepository,
                           JobTourServiceReadService jobTourServiceReadService,
                           ServiceEntityRepository serviceEntityRepository,
                           ServiceTypeRepository serviceTypeRepository,
                           ServiceHistoryRepository serviceHistoryRepository) {
        this.customerRepository = customerRepository;
        this.costCenterRepository = costCenterRepository;
        this.courierRepository = courierRepository;
        this.employeeRepository = employeeRepository;
        this.companyRepository = companyRepository;
        this.userRepository = userRepository;
        this.addressRepository = addressRepository;
        this.tourRepository = tourRepository;
        this.headquartersRepository = headquartersRepository;
        this.parameterRepository = parameterRepository;
        this.jobRepository = jobRepository;
        this.jobTourServiceReadService = jobTourServiceReadService;
        this.serviceEntityRepository = serviceEntityRepository;
        this.serviceTypeRepository = serviceTypeRepository;
        this.serviceHistoryRepository = serviceHistoryRepository;
    }

    public List<CustomerListItemDto> findCustomersByHeadquarters(Long hqId) {
        return customerRepository.findByHeadquartersId(hqId).stream()
                .map(this::toCustomerListItem)
                .toList();
    }

    public List<CustomerListItemDto> searchCustomers(Long hqId, String term) {
        return customerRepository.searchCustomers(hqId, term).stream()
                .map(this::toCustomerListItem)
                .toList();
    }

    public Optional<CustomerDetailDto> findCustomerDetail(Long id) {
        return customerRepository.findById(Objects.requireNonNull(id)).map(this::toCustomerDetail);
    }

    public List<CourierListItemDto> findCouriersByHeadquarters(Long hqId) {
        return courierRepository.findByHeadquartersId(hqId).stream()
                .map(this::toCourierListItem)
                .toList();
    }

    public List<CourierListItemDto> searchCouriers(Long hqId, String term) {
        return courierRepository.searchCouriers(hqId, term).stream()
                .map(this::toCourierListItem)
                .toList();
    }

    public Optional<CourierDetailDto> findCourierDetail(Long id) {
        return courierRepository.findById(Objects.requireNonNull(id)).map(this::toCourierDetail);
    }

    public List<EmployeeListItemDto> findEmployees() {
        return employeeRepository.findAll().stream()
                .map(this::toEmployeeListItem)
                .toList();
    }

    public Optional<EmployeeDetailDto> findEmployeeDetail(Long id) {
        return employeeRepository.findById(Objects.requireNonNull(id)).map(this::toEmployeeDetail);
    }

    public List<TourDetailDto> findToursForJob(Long jobId) {
        return tourRepository.findByJobIdOrderBySort(jobId).stream()
                .map(this::toTourDetail)
                .toList();
    }

    public Optional<HeadquartersDetailDto> findHeadquartersDetail(Long id) {
        return headquartersRepository.findById(Objects.requireNonNull(id)).map(this::toHeadquartersDetail);
    }

    public StatisticsSummaryDto buildStatisticsSummary(Long hqId, LocalDate fromDate, LocalDate toDate) {
        List<Job> jobs = loadJobs(hqId, fromDate, toDate);
        return buildStatisticsSummary(hqId, fromDate, toDate, jobs);
    }

    public StatisticsSummaryDto buildStatisticsSummaryForCostCenters(Long hqId,
                                                                     List<Long> accessibleCostCenterIds,
                                                                     LocalDate fromDate,
                                                                     LocalDate toDate) {
        List<Job> jobs = loadJobsForCostCenters(hqId, accessibleCostCenterIds, fromDate, toDate);
        return buildStatisticsSummary(hqId, fromDate, toDate, jobs);
    }

    private StatisticsSummaryDto buildStatisticsSummary(Long hqId,
                                                        LocalDate fromDate,
                                                        LocalDate toDate,
                                                        List<Job> jobs) {
        StatisticsSummaryDto dto = new StatisticsSummaryDto();
        dto.setHeadquartersId(hqId);
        dto.setFromDate(fromDate);
        dto.setToDate(toDate);
        dto.setTotalJobs(jobs.size());
        dto.setOpenJobs(jobs.stream().filter(job -> job.getStatus() != null && job.getStatus() < 8).count());
        dto.setDeliveredJobs(jobs.stream().filter(job -> job.getStatus() != null && job.getStatus() >= 8 && job.getStatus() < 10).count());
        dto.setCancelledJobs(jobs.stream().filter(job -> Integer.valueOf(10).equals(job.getStatus())).count());
        dto.setTodaysJobs(jobs.stream()
                .filter(job -> job.getOrderTime() != null && LocalDate.now().equals(job.getOrderTime().toLocalDate()))
                .count());
        dto.setCustomerCount(customerRepository.findByHeadquartersId(hqId).size());
        dto.setCourierCount(courierRepository.findByHeadquartersId(hqId).size());
        dto.setEmployeeCount(employeeRepository.findAll().stream()
                .filter(employee -> hqId.equals(employee.getHeadquartersId()))
                .count());
        dto.setTotalRevenue(sumJobs(jobs, true));
        dto.setTotalCourierCosts(sumJobs(jobs, false));
        return dto;
    }

    public InvoiceSummaryDto buildInvoiceSummary(Long hqId, LocalDate fromDate, LocalDate toDate) {
        List<Job> jobs = loadJobs(hqId, fromDate, toDate);
        return buildInvoiceSummary(hqId, fromDate, toDate, jobs);
    }

    public InvoiceSummaryDto buildInvoiceSummaryForCostCenters(Long hqId,
                                                               List<Long> accessibleCostCenterIds,
                                                               LocalDate fromDate,
                                                               LocalDate toDate) {
        List<Job> jobs = loadJobsForCostCenters(hqId, accessibleCostCenterIds, fromDate, toDate);
        return buildInvoiceSummary(hqId, fromDate, toDate, jobs);
    }

    private InvoiceSummaryDto buildInvoiceSummary(Long hqId,
                                                  LocalDate fromDate,
                                                  LocalDate toDate,
                                                  List<Job> jobs) {
        List<Job> invoiceableJobs = filterInvoiceJobs(jobs, hqId, fromDate, toDate);
        Map<Long, BigDecimal> invoiceRevenueByJobId = resolveInvoiceRevenueByJobId(invoiceableJobs, null);
        BigDecimal totalRevenue = sumInvoiceRevenue(invoiceableJobs, invoiceRevenueByJobId);
        BigDecimal totalCourierCosts = sumJobs(invoiceableJobs, false);

        InvoiceSummaryDto dto = new InvoiceSummaryDto();
        dto.setHeadquartersId(hqId);
        dto.setFromDate(fromDate);
        dto.setToDate(toDate);
        dto.setTotalJobs(jobs.size());
        dto.setBillableJobs(invoiceableJobs.size());
        dto.setTotalRevenue(totalRevenue);
        dto.setTotalCourierCosts(totalCourierCosts);
        dto.setContributionMargin(totalRevenue.subtract(totalCourierCosts));
        return dto;
    }

    public List<InvoiceCustomerSummaryDto> findInvoiceCustomers(Long hqId, LocalDate fromDate, LocalDate toDate) {
        return findInvoiceCustomers(hqId, null, null, fromDate, toDate);
    }

    public List<InvoiceCustomerSummaryDto> findInvoiceCustomers(Long hqId,
                                                                Long customerId,
                                                                List<Long> accessibleCostCenterIds,
                                                                LocalDate fromDate,
                                                                LocalDate toDate) {
        List<InvoiceCustomerSummaryDto> rows = new ArrayList<>();

        List<Customer> customers = customerId != null
                ? customerRepository.findById(customerId).stream().toList()
                : customerRepository.findByHeadquartersId(hqId);

        for (Customer customer : customers) {
            List<CostCenter> costCenters = filterCostCentersByAccess(
                    findVisibleCostCentersSorted(customer.getId()),
                    accessibleCostCenterIds
            );
            if (costCenters.isEmpty()) {
                continue;
            }

            List<Job> jobs = filterInvoiceJobs(
                    jobRepository.findByPayerCostCenters(costCenters.stream().map(CostCenter::getId).toList()),
                    hqId,
                    fromDate,
                    toDate
            );
            if (jobs.isEmpty()) {
                continue;
            }

            Set<Long> customerCostCenterIds = new LinkedHashSet<>();
            for (CostCenter costCenter : costCenters) {
                if (costCenter.getId() != null) {
                    customerCostCenterIds.add(costCenter.getId());
                }
            }
            Map<Long, BigDecimal> invoiceRevenueByJobId = resolveInvoiceRevenueByJobId(jobs, customerCostCenterIds);

            InvoiceCustomerSummaryDto dto = new InvoiceCustomerSummaryDto();
            dto.setCustomerId(customer.getId());
            dto.setCustomerEid(customer.getEid());
            dto.setCustomerName(resolveCustomerName(customer));
            dto.setInvoiceableJobs(jobs.size());
            dto.setTotalRevenue(sumInvoiceRevenue(jobs, invoiceRevenueByJobId));
            rows.add(dto);
        }

        rows.sort(Comparator.comparing(InvoiceCustomerSummaryDto::getCustomerName, Comparator.nullsLast(String::compareToIgnoreCase)));
        return rows;
    }

    public List<InvoiceJobRowDto> findInvoiceItems(Long hqId, Long customerId, Long costCenterId, LocalDate fromDate, LocalDate toDate) {
        return findInvoiceItems(hqId, customerId, costCenterId, null, fromDate, toDate);
    }

    public List<InvoiceJobRowDto> findInvoiceItems(Long hqId,
                                                   Long customerId,
                                                   Long costCenterId,
                                                   List<Long> accessibleCostCenterIds,
                                                   LocalDate fromDate,
                                                   LocalDate toDate) {
        if (customerId == null) {
            return List.of();
        }

        List<CostCenter> visibleCostCenters = filterCostCentersByAccess(
                findVisibleCostCentersSorted(customerId),
                accessibleCostCenterIds
        );
        if (visibleCostCenters.isEmpty()) {
            return List.of();
        }

        List<CostCenter> selectedCostCenters = costCenterId != null
                ? visibleCostCenters.stream().filter(costCenter -> costCenterId.equals(costCenter.getId())).toList()
                : visibleCostCenters;
        if (selectedCostCenters.isEmpty()) {
            return List.of();
        }

        List<Job> jobs = filterInvoiceJobs(
                jobRepository.findByPayerCostCenters(selectedCostCenters.stream().map(CostCenter::getId).toList()),
                hqId,
                fromDate,
                toDate
        );
        if (jobs.isEmpty()) {
            return List.of();
        }

        Map<Long, CostCenter> costCenterById = new HashMap<>();
        for (CostCenter costCenter : visibleCostCenters) {
            costCenterById.put(costCenter.getId(), costCenter);
        }

        Set<Long> selectedCostCenterIds = new LinkedHashSet<>();
        for (CostCenter selectedCostCenter : selectedCostCenters) {
            if (selectedCostCenter.getId() != null) {
                selectedCostCenterIds.add(selectedCostCenter.getId());
            }
        }

        Map<Long, List<Tour>> toursByJobId = loadToursByJobId(jobs);
        Map<Long, Map<Long, BigDecimal>> invoiceRevenueByCostCenterByJobId = jobTourServiceReadService.findBusinessVolumeByJobIds(
                jobs.stream().map(Job::getId).toList()
        );
        Map<Long, List<JobTourServiceRowDto>> invoiceServiceRowsByJobId = resolveInvoiceServiceRowsByJobId(customerId, hqId, jobs);
        InvoiceServiceDisplayOptions invoiceServiceDisplayOptions = resolveInvoiceServiceDisplayOptions(hqId);

        String customerName = customerRepository.findById(customerId)
                .map(this::resolveCustomerName)
                .orElse("");

        return jobs.stream()
                .sorted(Comparator.comparing(Job::getFinishTime, Comparator.nullsLast(LocalDateTime::compareTo))
                        .thenComparing(Job::getId))
                .map(job -> {
                    List<Tour> tours = toursByJobId.getOrDefault(job.getId(), List.of());
                    Long fallbackResolvedCostCenterId = resolveCostCenterId(job, tours);
                    InvoicePresentation presentation = resolveInvoicePresentation(
                            job,
                            costCenterId,
                            fallbackResolvedCostCenterId,
                            selectedCostCenterIds,
                            costCenterById,
                            invoiceRevenueByCostCenterByJobId.get(job.getId()),
                            invoiceServiceRowsByJobId.get(job.getId()),
                            invoiceServiceDisplayOptions
                    );
                    return toInvoiceJobRow(
                            job,
                            customerId,
                            customerName,
                            tours,
                            presentation
                    );
                })
                .toList();
    }

    public List<InvoiceJobRowDto> findCourierInvoiceItems(Long courierId,
                                                          LocalDate fromDate,
                                                          LocalDate toDate,
                                                          boolean finishedOnly) {
        if (courierId == null) {
            return List.of();
        }

        List<Job> jobs = jobRepository.findByCourierId(courierId).stream()
                .filter(job -> matchesCourierHistoryMode(job, finishedOnly))
                .filter(job -> matchesCourierHistoryDate(job, fromDate, toDate, finishedOnly))
                .filter(job -> !Integer.valueOf(10).equals(job.getStatus()))
                .filter(job -> !"2".equals(job.getStorno()))
                .sorted(Comparator.<Job, LocalDateTime>comparing(
                        job -> finishedOnly ? job.getFinishTime() : job.getOrderTime(),
                        Comparator.nullsLast(LocalDateTime::compareTo)
                ).thenComparing(Job::getId))
                .toList();
        if (jobs.isEmpty()) {
            return List.of();
        }

        Map<Long, List<Tour>> toursByJobId = new LinkedHashMap<>();
        for (Tour tour : tourRepository.findByJobIdInOrderByJobIdAscSortAsc(jobs.stream().map(Job::getId).toList())) {
            toursByJobId.computeIfAbsent(tour.getJobId(), ignored -> new ArrayList<>()).add(tour);
        }

        Set<Long> resolvedCostCenterIds = new LinkedHashSet<>();
        for (Job job : jobs) {
            Long costCenterId = resolveCostCenterId(job, toursByJobId.getOrDefault(job.getId(), List.of()));
            if (costCenterId != null) {
                resolvedCostCenterIds.add(costCenterId);
            }
        }

        Map<Long, CostCenter> costCenterById = new HashMap<>();
        for (CostCenter costCenter : costCenterRepository.findAllById(resolvedCostCenterIds)) {
            costCenterById.put(costCenter.getId(), costCenter);
        }

        Map<Long, String> customerNameById = new HashMap<>();
        for (CostCenter costCenter : costCenterById.values()) {
            Long customerId = costCenter.getCustomerId();
            if (customerId != null && !customerNameById.containsKey(customerId)) {
                customerRepository.findById(customerId)
                        .ifPresent(customer -> customerNameById.put(customerId, resolveCustomerName(customer)));
            }
        }

        List<InvoiceJobRowDto> rows = new ArrayList<>();
        for (Job job : jobs) {
            List<Tour> tours = toursByJobId.getOrDefault(job.getId(), List.of());
            Long costCenterId = resolveCostCenterId(job, tours);
            Long customerId = costCenterId != null && costCenterById.containsKey(costCenterId)
                    ? costCenterById.get(costCenterId).getCustomerId()
                    : null;
            rows.add(toInvoiceJobRow(
                    job,
                    customerId,
                    customerId != null ? customerNameById.getOrDefault(customerId, "") : "",
                    tours,
                    new InvoicePresentation(
                            costCenterId,
                            resolveCostCenterName(costCenterId, costCenterById),
                            null,
                            ""
                    )
            ));
        }
        return rows;
    }

    public List<ServicePriceRowDto> findServicePrices(Long hqId, Long customerId) {
        Long effectiveCustomerId = customerId != null ? customerId : 0L;
        Map<Long, ServiceType> serviceTypes = new HashMap<>();
        for (ServiceType serviceType : serviceTypeRepository.findAvailableForCustomer(hqId, effectiveCustomerId)) {
            serviceTypes.put(serviceType.getId(), serviceType);
        }

        Map<String, ServiceHistory> latestByService = new LinkedHashMap<>();
        for (ServiceHistory history : serviceHistoryRepository.findForHqAndCustomer(hqId, effectiveCustomerId)) {
            latestByService.putIfAbsent(serviceHistoryKey(history.getServiceId(), history.getServiceTypeId()), history);
        }

        return serviceEntityRepository.findAvailableForCustomer(hqId, effectiveCustomerId).stream()
                .sorted(Comparator.comparing(ServiceEntity::getName, Comparator.nullsLast(String::compareToIgnoreCase)))
                .map(service -> toServicePriceRow(
                        service,
                        serviceTypes.get(service.getServiceTypeId()),
                        latestByService.get(serviceHistoryKey(service.getId(), service.getServiceTypeId()))
                ))
                .toList();
    }

    public List<CostCenter> findJobFilterCostCenters(Long hqId, List<Long> accessibleCostCenterIds) {
        List<CostCenter> costCenters = new ArrayList<>();
        for (Customer customer : customerRepository.findByHeadquartersId(hqId)) {
            costCenters.addAll(findVisibleCostCentersSorted(customer.getId()));
        }
        if (accessibleCostCenterIds != null) {
            costCenters = filterCostCentersByAccess(costCenters, accessibleCostCenterIds);
        }
        return costCenters.stream()
                .sorted(Comparator
                        .comparing(CostCenter::getPath, Comparator.nullsLast(String::compareToIgnoreCase))
                        .thenComparing(CostCenter::getName, Comparator.nullsLast(String::compareToIgnoreCase)))
                .toList();
    }

    public List<JobListRowDto> findJobListRows(Long hqId,
                                               LocalDate fromDate,
                                               LocalDate toDate,
                                               String term,
                                               Integer status,
                                               Long costCenterId,
                                               String courierSid,
                                               Boolean exportedOnly) {
        return findJobListRows(loadJobs(hqId, fromDate, toDate), term, status, costCenterId, courierSid, exportedOnly);
    }

    public List<JobListRowDto> findJobListRowsForCostCenters(Long hqId,
                                                             List<Long> accessibleCostCenterIds,
                                                             LocalDate fromDate,
                                                             LocalDate toDate,
                                                             String term,
                                                             Integer status,
                                                             Long costCenterId,
                                                             String courierSid,
                                                             Boolean exportedOnly) {
        return findJobListRows(loadJobsForCostCenters(hqId, accessibleCostCenterIds, fromDate, toDate),
                term, status, costCenterId, courierSid, exportedOnly);
    }

    private CustomerListItemDto toCustomerListItem(Customer customer) {
        CustomerDetailDto detail = toCustomerDetail(customer);
        CustomerListItemDto dto = new CustomerListItemDto();
        dto.setId(detail.getId());
        dto.setEid(detail.getEid());
        dto.setCompanyName(detail.getCompanyName());
        dto.setContact(joinName(detail.getFirstname(), detail.getName()));
        dto.setEmail(detail.getEmail());
        dto.setPhone(detail.getPhone());
        return dto;
    }

    private CustomerDetailDto toCustomerDetail(Customer customer) {
        CustomerDetailDto dto = new CustomerDetailDto();
        dto.setId(customer.getId());
        dto.setHeadquartersId(customer.getHeadquartersId());
        dto.setEid(customer.getEid());
        dto.setDiscount(customer.getDiscount());
        dto.setMarkup(customer.getMarkup());
        dto.setGroup(customer.getGroup());
        dto.setFilter(customer.getFilter());
        dto.setCommissionNo(customer.getCommissionNo());
        dto.setTracking(customer.getTracking());

        companyRepository.findById(Objects.requireNonNull(customer.getCompanyId())).ifPresent(company -> {
            dto.setCompanyName(company.getComp());
            dto.setCompanyName2(company.getComp2());
            dto.setCompanyName3(company.getComp3());
            dto.setCompanyName4(company.getComp4());
            dto.setHsno(company.getHsno());
            dto.setIln(company.getIln());
            dto.setTaxIdNo(company.getTaxIdNo());
            dto.setStaxIdNo(company.getStaxIdNo());
            fillAddress(company.getAddressId(), dto);
        });

        employeeRepository.findById(Objects.requireNonNull(customer.getAdminEmployeeId()))
                .flatMap(employee -> userRepository.findById(Objects.requireNonNull(employee.getUserId())))
                .ifPresent(user -> fillUser(dto, user));

        return dto;
    }

    private CourierListItemDto toCourierListItem(Courier courier) {
        CourierDetailDto detail = toCourierDetail(courier);
        CourierListItemDto dto = new CourierListItemDto();
        dto.setId(detail.getId());
        dto.setEid(detail.getEid());
        dto.setSid(detail.getSid());
        dto.setCompanyName(detail.getCompanyName());
        dto.setContact(joinName(detail.getFirstname(), detail.getName()));
        dto.setAvailable("1".equals(detail.getAvailable()) ? "Ja" : "Nein");
        return dto;
    }

    private CourierDetailDto toCourierDetail(Courier courier) {
        CourierDetailDto dto = new CourierDetailDto();
        dto.setId(courier.getId());
        dto.setHeadquartersId(courier.getHeadquartersId());
        dto.setEid(courier.getEid());
        dto.setSid(courier.getSid());
        dto.setAvailable(courier.getAvailable());
        dto.setVehicleTypeId(courier.getVehicleTypeId());
        dto.setMobilePda(courier.getMobilePda());
        dto.setLocationZipcode(courier.getLocationZipcode());
        dto.setGpsLongitude(courier.getGpsLongitude());
        dto.setGpsLatitude(courier.getGpsLatitude());
        dto.setGpsTime(courier.getGpsTime());
        dto.setGpsType(courier.getGpsType());
        dto.setAvailableTime(courier.getAvailableTime());
        dto.setGroup(courier.getGroup());
        dto.setFilter(courier.getFilter());

        companyRepository.findById(Objects.requireNonNull(courier.getCompanyId())).ifPresent(company -> {
            dto.setCompanyName(company.getComp());
            dto.setCompanyName2(company.getComp2());
            dto.setHsno(company.getHsno());
            dto.setIln(company.getIln());
            dto.setTaxIdNo(company.getTaxIdNo());
            fillAddress(company.getAddressId(), dto);
        });

        userRepository.findById(Objects.requireNonNull(courier.getUserId())).ifPresent(user -> {
            dto.setAccount(user.getAccount());
            dto.setName(user.getName());
            dto.setFirstname(user.getFirstname());
            dto.setEmail(user.getEmail());
            dto.setPhone(user.getPhone());
            dto.setPhone2(user.getPhone2());
            dto.setFax(user.getFax());
            dto.setBirthdate(user.getBirthdate());
            dto.setCountry(user.getCountry());
        });

        return dto;
    }

    private EmployeeListItemDto toEmployeeListItem(Employee employee) {
        EmployeeDetailDto detail = toEmployeeDetail(employee);
        EmployeeListItemDto dto = new EmployeeListItemDto();
        dto.setId(detail.getId());
        dto.setUserId(detail.getUserId());
        dto.setHeadquartersId(detail.getHeadquartersId());
        dto.setCustomerId(detail.getCustomerId());
        dto.setUserName(detail.getName());
        dto.setUserFirstname(detail.getFirstname());
        dto.setUserEmail(detail.getEmail());
        dto.setHeadquarters(detail.getHeadquarters());
        dto.setUserType(detail.getUserType());
        return dto;
    }

    private EmployeeDetailDto toEmployeeDetail(Employee employee) {
        EmployeeDetailDto dto = new EmployeeDetailDto();
        dto.setId(employee.getId());
        dto.setUserId(employee.getUserId());
        dto.setHeadquartersId(employee.getHeadquartersId());
        dto.setCostCenterId(employee.getCostCenterId());
        dto.setRights(employee.getRights());
        dto.setGroup(employee.getGroup());
        if (employee.getCostCenterId() != null) {
            costCenterRepository.findById(Objects.requireNonNull(employee.getCostCenterId()))
                    .map(CostCenter::getCustomerId)
                    .ifPresent(dto::setCustomerId);
        }
        dto.setHeadquarters(employee.getHeadquarters());

        userRepository.findById(Objects.requireNonNull(employee.getUserId())).ifPresent(user -> {
            dto.setAccount(user.getAccount());
            dto.setName(user.getName());
            dto.setFirstname(user.getFirstname());
            dto.setEmail(user.getEmail());
            dto.setPhone(user.getPhone());
            dto.setPhone2(user.getPhone2());
            dto.setBirthdate(user.getBirthdate());
            dto.setUserType(user.getType());
        });

        return dto;
    }

    private TourDetailDto toTourDetail(Tour tour) {
        TourDetailDto dto = new TourDetailDto();
        dto.setId(tour.getId());
        dto.setJobId(tour.getJobId());
        dto.setSort(tour.getSort());
        dto.setComp(tour.getComp());
        dto.setPerson(tour.getPerson());
        dto.setAddressId(tour.getAddressId());
        dto.setHsno(tour.getHsno());
        dto.setCommissionNo(tour.getCommissionNo());
        dto.setStatus(tour.getStatus());
        dto.setRemark(tour.getRemark());
        dto.setPhone(tour.getPhone());
        dto.setCostCenterId(tour.getCostCenterId());

        addressRepository.findById(Objects.requireNonNull(tour.getAddressId())).ifPresent(address -> {
            dto.setStreet(address.getStreet());
            dto.setZipcode(address.getZipcode());
            dto.setCity(address.getCity());
        });
        return dto;
    }

    private HeadquartersDetailDto toHeadquartersDetail(Headquarters headquarters) {
        HeadquartersDetailDto dto = new HeadquartersDetailDto();
        dto.setId(headquarters.getId());
        dto.setMnemonic(headquarters.getMnemonic());
        dto.setName(headquarters.getName());
        dto.setWorkmode(headquarters.getWorkmode());
        dto.setCompanyId(headquarters.getCompanyId());

        companyRepository.findById(Objects.requireNonNull(headquarters.getCompanyId())).ifPresent(company -> {
            dto.setCompanyName(company.getComp());
            dto.setCompanyName2(company.getComp2());
            dto.setCompanyName3(company.getComp3());
            dto.setCompanyName4(company.getComp4());
            dto.setHsno(company.getHsno());
            addressRepository.findById(Objects.requireNonNull(company.getAddressId())).ifPresent(address -> {
                dto.setStreet(address.getStreet());
                dto.setZipcode(address.getZipcode());
                dto.setCity(address.getCity());
            });
        });

        dto.setBwvPhone(findParameterValue("BWV_PHONE_NO", headquarters.getId()));
        dto.setGln(findParameterValue("GLN_HQ_ALL", 0L));
        dto.setDuns(findParameterValue("DUNS_HQ_ALL", 0L));
        return dto;
    }

    private ServicePriceRowDto toServicePriceRow(ServiceEntity service,
                                                 ServiceType serviceType,
                                                 ServiceHistory serviceHistory) {
        ServicePriceRowDto dto = new ServicePriceRowDto();
        dto.setServiceId(service.getId());
        dto.setServiceName(service.getName());
        dto.setServiceMode(service.getMode());
        dto.setServiceTypeId(service.getServiceTypeId());
        dto.setServiceTypeName(serviceType != null ? serviceType.getName() : "");
        dto.setCustomerId(service.getCustomerId());
        if (serviceHistory != null) {
            dto.setValidFrom(serviceHistory.getValidFrom() != null ? serviceHistory.getValidFrom().toLocalDate() : null);
            dto.setPrice(serviceHistory.getPrice());
            dto.setCourierPrice(serviceHistory.getCourierPrice());
            dto.setDiscount(serviceHistory.getDiscount());
        }
        return dto;
    }

    private String serviceHistoryKey(Long serviceId, Long serviceTypeId) {
        return String.valueOf(serviceId) + ":" + String.valueOf(serviceTypeId);
    }

    private void fillUser(CustomerDetailDto dto, User user) {
        dto.setAccount(user.getAccount());
        dto.setName(user.getName());
        dto.setFirstname(user.getFirstname());
        dto.setEmail(user.getEmail());
        dto.setPhone(user.getPhone());
        dto.setPhone2(user.getPhone2());
        dto.setFax(user.getFax());
    }

    private void fillAddress(Long addressId, CustomerDetailDto dto) {
        addressRepository.findById(Objects.requireNonNull(addressId)).ifPresent(address -> {
            dto.setStreet(address.getStreet());
            dto.setZipcode(address.getZipcode());
            dto.setCity(address.getCity());
        });
    }

    private void fillAddress(Long addressId, CourierDetailDto dto) {
        addressRepository.findById(Objects.requireNonNull(addressId)).ifPresent(address -> {
            dto.setStreet(address.getStreet());
            dto.setZipcode(address.getZipcode());
            dto.setCity(address.getCity());
        });
    }

    private List<CostCenter> findVisibleCostCentersSorted(Long customerId) {
        return costCenterRepository.findVisibleByCustomerId(customerId).stream()
                .filter(costCenter -> !"1".equals(costCenter.getIsExtern()))
                .sorted(Comparator
                        .comparing(CostCenter::getPath, Comparator.nullsLast(String::compareToIgnoreCase))
                        .thenComparing(CostCenter::getName, Comparator.nullsLast(String::compareToIgnoreCase)))
                .toList();
    }

    private List<CostCenter> filterCostCentersByAccess(List<CostCenter> costCenters, List<Long> accessibleCostCenterIds) {
        if (accessibleCostCenterIds == null) {
            return costCenters;
        }
        if (accessibleCostCenterIds.isEmpty()) {
            return List.of();
        }
        return costCenters.stream()
                .filter(costCenter -> accessibleCostCenterIds.contains(costCenter.getId()))
                .toList();
    }

    private List<Job> filterInvoiceJobs(List<Job> jobs, Long hqId, LocalDate fromDate, LocalDate toDate) {
        return jobs.stream()
                .filter(job -> hqId.equals(job.getHeadquartersId()))
                .filter(job -> job.getFinishTime() != null)
                .filter(job -> fromDate == null || !job.getFinishTime().toLocalDate().isBefore(fromDate))
                .filter(job -> toDate == null || !job.getFinishTime().toLocalDate().isAfter(toDate))
                .filter(job -> !Integer.valueOf(10).equals(job.getStatus()))
                .filter(job -> !"2".equals(job.getStorno()))
                .toList();
    }

    private Map<Long, List<Tour>> loadToursByJobId(List<Job> jobs) {
        if (jobs.isEmpty()) {
            return Map.of();
        }
        Map<Long, List<Tour>> toursByJobId = new LinkedHashMap<>();
        for (Tour tour : tourRepository.findByJobIdInOrderByJobIdAscSortAsc(jobs.stream().map(Job::getId).toList())) {
            toursByJobId.computeIfAbsent(tour.getJobId(), ignored -> new ArrayList<>()).add(tour);
        }
        return toursByJobId;
    }

    private Map<Long, BigDecimal> resolveInvoiceRevenueByJobId(List<Job> jobs, Set<Long> relevantCostCenterIds) {
        if (jobs.isEmpty()) {
            return Map.of();
        }

        Map<Long, Map<Long, BigDecimal>> businessVolumeByJobId = jobTourServiceReadService.findBusinessVolumeByJobIds(
                jobs.stream().map(Job::getId).toList()
        );
        Map<Long, BigDecimal> result = new LinkedHashMap<>();
        for (Job job : jobs) {
            result.put(job.getId(), resolveInvoiceRevenue(job, relevantCostCenterIds, businessVolumeByJobId.get(job.getId())));
        }
        return result;
    }

    private BigDecimal resolveInvoiceRevenue(Job job,
                                             Set<Long> relevantCostCenterIds,
                                             Map<Long, BigDecimal> businessVolumeByCostCenterId) {
        if (businessVolumeByCostCenterId != null && !businessVolumeByCostCenterId.isEmpty()) {
            BigDecimal matchingSpecificValue = sumSpecificBusinessVolume(businessVolumeByCostCenterId, relevantCostCenterIds);
            if (matchingSpecificValue != null) {
                return matchingSpecificValue;
            }
            BigDecimal globalValue = businessVolumeByCostCenterId.get(0L);
            if (globalValue != null) {
                return globalValue;
            }
            if (businessVolumeByCostCenterId.size() == 1) {
                return businessVolumeByCostCenterId.values().iterator().next();
            }
        }
        return job.getTotalPrice() != null ? job.getTotalPrice() : BigDecimal.ZERO;
    }

    private boolean matchesCourierHistoryMode(Job job, boolean finishedOnly) {
        return finishedOnly ? job.getFinishTime() != null : job.getFinishTime() == null;
    }

    private boolean matchesCourierHistoryDate(Job job, LocalDate fromDate, LocalDate toDate, boolean finishedOnly) {
        LocalDateTime relevantDateTime = finishedOnly ? job.getFinishTime() : job.getOrderTime();
        if (relevantDateTime == null) {
            return false;
        }
        LocalDate relevantDate = relevantDateTime.toLocalDate();
        if (fromDate != null && relevantDate.isBefore(fromDate)) {
            return false;
        }
        return toDate == null || !relevantDate.isAfter(toDate);
    }

    private List<JobListRowDto> findJobListRows(List<Job> jobs,
                                                String term,
                                                Integer status,
                                                Long costCenterId,
                                                String courierSid,
                                                Boolean exportedOnly) {
        if (jobs.isEmpty()) {
            return List.of();
        }

        Map<Long, List<Tour>> toursByJobId = loadToursByJobId(jobs);
        Map<Long, BigDecimal> jobListRevenueByJobId = resolveJobListRevenueByJobId(jobs);

        List<Long> resolvedCostCenterIds = new ArrayList<>();
        for (Job job : jobs) {
            Long resolvedCostCenterId = resolveCostCenterId(job, toursByJobId.getOrDefault(job.getId(), List.of()));
            if (resolvedCostCenterId != null && !resolvedCostCenterIds.contains(resolvedCostCenterId)) {
                resolvedCostCenterIds.add(resolvedCostCenterId);
            }
        }

        Map<Long, CostCenter> costCenterById = new HashMap<>();
        for (CostCenter center : costCenterRepository.findAllById(resolvedCostCenterIds)) {
            costCenterById.put(center.getId(), center);
        }

        Map<Long, String> customerNameById = new HashMap<>();
        for (CostCenter center : costCenterById.values()) {
            Long customerId = center.getCustomerId();
            if (customerId != null && !customerNameById.containsKey(customerId)) {
                customerRepository.findById(customerId)
                        .ifPresent(customer -> customerNameById.put(customerId, resolveCustomerName(customer)));
            }
        }

        String normalizedTerm = term != null ? term.trim().toLowerCase() : "";
        String normalizedCourierSid = courierSid != null ? courierSid.trim().toLowerCase() : "";

        return jobs.stream()
                .sorted(Comparator
                        .comparing(Job::getOrderTime, Comparator.nullsLast(LocalDateTime::compareTo))
                        .thenComparing(Job::getId, Comparator.nullsLast(Long::compareTo))
                        .reversed())
                .map(job -> toJobListRow(job,
                        costCenterById,
                        customerNameById,
                        toursByJobId.getOrDefault(job.getId(), List.of()),
                        jobListRevenueByJobId.get(job.getId())))
                .filter(row -> matchesJobListFilters(row, normalizedTerm, status, costCenterId, normalizedCourierSid, exportedOnly))
                .toList();
    }

    private boolean matchesJobListFilters(JobListRowDto row,
                                          String normalizedTerm,
                                          Integer status,
                                          Long costCenterId,
                                          String normalizedCourierSid,
                                          Boolean exportedOnly) {
        if (status != null && !status.equals(row.getStatus())) {
            return false;
        }
        if (costCenterId != null && !costCenterId.equals(row.getCostCenterId())) {
            return false;
        }
        if (normalizedCourierSid != null && !normalizedCourierSid.isBlank()) {
            String candidate = row.getCourierSid() != null ? row.getCourierSid().toLowerCase() : "";
            if (!candidate.contains(normalizedCourierSid)) {
                return false;
            }
        }
        if (Boolean.TRUE.equals(exportedOnly) && !row.isExported()) {
            return false;
        }
        if (Boolean.FALSE.equals(exportedOnly) && row.isExported()) {
            return false;
        }
        if (normalizedTerm == null || normalizedTerm.isBlank()) {
            return true;
        }
        if (String.valueOf(row.getId()).equals(normalizedTerm)) {
            return true;
        }
        return containsIgnoreCase(row.getTourName(), normalizedTerm)
                || containsIgnoreCase(row.getCustomerName(), normalizedTerm)
                || containsIgnoreCase(row.getCostCenterName(), normalizedTerm)
                || containsIgnoreCase(row.getCommissionNo(), normalizedTerm)
                || containsIgnoreCase(row.getPickupName(), normalizedTerm)
                || containsIgnoreCase(row.getDeliveryName(), normalizedTerm)
                || containsIgnoreCase(row.getCourierSid(), normalizedTerm);
    }

    private boolean containsIgnoreCase(String value, String normalizedTerm) {
        return value != null && value.toLowerCase().contains(normalizedTerm);
    }

    private InvoiceJobRowDto toInvoiceJobRow(Job job,
                                             Long customerId,
                                             String customerName,
                                             List<Tour> tours,
                                             InvoicePresentation presentation) {
        InvoiceJobRowDto dto = new InvoiceJobRowDto();
        dto.setJobId(job.getId());
        dto.setCustomerId(customerId);
        dto.setCustomerName(customerName);
        dto.setOrderTime(job.getOrderTime());
        dto.setFinishTime(job.getFinishTime());
        dto.setCourierSid(job.getCourierSid());
        dto.setTourName(job.getTourName());
        dto.setStatus(job.getStatus());
        dto.setIncomplete(job.getIncomplete());
        BigDecimal effectiveRevenue = presentation != null && presentation.revenue() != null
                ? presentation.revenue()
                : job.getTotalPrice();
        dto.setTotalPrice(effectiveRevenue);
        dto.setCourierPrice(job.getCourierPrice());
        dto.setMargin(safeSubtract(effectiveRevenue, job.getCourierPrice()));
        dto.setExported(job.getExportTime() != null);
        dto.setServiceDetails(presentation != null ? presentation.serviceDetails() : "");
        dto.setInvoiceText(job.getInvoiceText());

        Tour pickup = tours.stream().filter(tour -> Integer.valueOf(1).equals(tour.getSort())).findFirst().orElse(null);
        Tour delivery = tours.stream().filter(tour -> Integer.valueOf(2).equals(tour.getSort())).findFirst().orElse(null);

        dto.setCommissionNo(firstNonBlank(
                pickup != null ? pickup.getCommissionNo() : null,
                delivery != null ? delivery.getCommissionNo() : null
        ));
        dto.setPickupName(formatTourStop(pickup));
        dto.setDeliveryName(formatTourStop(delivery));

        dto.setCostCenterId(presentation != null ? presentation.costCenterId() : resolveCostCenterId(job, tours));
        dto.setCostCenterName(presentation != null ? presentation.costCenterName() : "");
        return dto;
    }

    private Map<Long, List<JobTourServiceRowDto>> resolveInvoiceServiceRowsByJobId(Long customerId,
                                                                                    Long hqId,
                                                                                    List<Job> jobs) {
        if (jobs.isEmpty() || customerId == null || !usePersistedTourserviceDetailsForInvoices(customerId, hqId)) {
            return Map.of();
        }
        return jobTourServiceReadService.findRowsByJobIds(jobs.stream().map(Job::getId).toList());
    }

    private boolean usePersistedTourserviceDetailsForInvoices(Long customerId, Long hqId) {
        String key = ParameterKeys.MASK_INVOICE_SHOW_SERVICES + customerId;
        return "1".equals(findParameterValueWithFallback(key, hqId));
    }

    private InvoiceServiceDisplayOptions resolveInvoiceServiceDisplayOptions(Long hqId) {
        return new InvoiceServiceDisplayOptions(
                "1".equals(findParameterValueWithFallback(ParameterKeys.INV_PRINT_METATYPE_SERVICE, hqId)),
                "1".equals(findParameterValueWithFallback(ParameterKeys.INV_PRINT_DISCOUNT, hqId)),
                "1".equals(findParameterValueWithFallback(ParameterKeys.INV_PRINT_SRVPRICE, hqId))
        );
    }

    private InvoicePresentation resolveInvoicePresentation(Job job,
                                                           Long explicitCostCenterId,
                                                           Long fallbackResolvedCostCenterId,
                                                           Set<Long> relevantCostCenterIds,
                                                           Map<Long, CostCenter> costCenterById,
                                                           Map<Long, BigDecimal> businessVolumeByCostCenterId,
                                                           List<JobTourServiceRowDto> rows,
                                                           InvoiceServiceDisplayOptions displayOptions) {
        List<Long> matchingCostCenterIds = resolveMatchingInvoiceCostCenterIds(relevantCostCenterIds, businessVolumeByCostCenterId, rows);
        Long displayCostCenterId = explicitCostCenterId != null
                ? explicitCostCenterId
                : matchingCostCenterIds.size() == 1
                ? matchingCostCenterIds.get(0)
                : fallbackResolvedCostCenterId;
        String costCenterName = resolveInvoiceCostCenterName(
                explicitCostCenterId,
                matchingCostCenterIds,
                fallbackResolvedCostCenterId,
                costCenterById
        );
        return new InvoicePresentation(
                explicitCostCenterId == null && matchingCostCenterIds.size() > 1 ? null : displayCostCenterId,
                costCenterName,
                resolveInvoiceRevenue(job, relevantCostCenterIds, businessVolumeByCostCenterId),
                resolveInvoiceServiceDetails(
                        explicitCostCenterId,
                        fallbackResolvedCostCenterId,
                        relevantCostCenterIds,
                        costCenterById,
                        rows,
                        displayOptions
                )
        );
    }

    private String resolveInvoiceServiceDetails(Long explicitCostCenterId,
                                                Long fallbackResolvedCostCenterId,
                                                Set<Long> relevantCostCenterIds,
                                                Map<Long, CostCenter> costCenterById,
                                                List<JobTourServiceRowDto> rows,
                                                InvoiceServiceDisplayOptions displayOptions) {
        if (rows == null || rows.isEmpty()) {
            return "";
        }

        if (explicitCostCenterId != null) {
            List<JobTourServiceRowDto> explicitRows = filterRowsByCostCenter(rows, explicitCostCenterId);
            if (!explicitRows.isEmpty()) {
                return formatInvoiceServiceRows(explicitRows, displayOptions);
            }
        }

        List<Long> matchingCostCenterIds = resolveMatchingInvoiceCostCenterIds(relevantCostCenterIds, null, rows);
        if (!matchingCostCenterIds.isEmpty()) {
            if (matchingCostCenterIds.size() == 1) {
                return formatInvoiceServiceRows(filterRowsByCostCenter(rows, matchingCostCenterIds.get(0)), displayOptions);
            }
            StringBuilder details = new StringBuilder();
            for (Long costCenterId : matchingCostCenterIds) {
                List<JobTourServiceRowDto> costCenterRows = filterRowsByCostCenter(rows, costCenterId);
                String block = formatInvoiceServiceRows(costCenterRows, displayOptions);
                if (block.isBlank()) {
                    continue;
                }
                if (details.length() > 0) {
                    details.append("\n\n");
                }
                details.append(resolveCostCenterName(costCenterId, costCenterById));
                details.append(":\n");
                details.append(block);
            }
            if (details.length() > 0) {
                return details.toString();
            }
        }

        List<JobTourServiceRowDto> globalRows = filterRowsByCostCenter(rows, 0L);
        if (!globalRows.isEmpty()) {
            return formatInvoiceServiceRows(globalRows, displayOptions);
        }
        if (fallbackResolvedCostCenterId != null) {
            List<JobTourServiceRowDto> fallbackRows = filterRowsByCostCenter(rows, fallbackResolvedCostCenterId);
            if (!fallbackRows.isEmpty()) {
                return formatInvoiceServiceRows(fallbackRows, displayOptions);
            }
        }
        return formatInvoiceServiceRows(rows, displayOptions);
    }

    private JobListRowDto toJobListRow(Job job,
                                       Map<Long, CostCenter> costCenterById,
                                       Map<Long, String> customerNameById,
                                       List<Tour> tours,
                                       BigDecimal totalPrice) {
        JobListRowDto dto = new JobListRowDto();
        dto.setId(job.getId());
        dto.setStatus(job.getStatus());
        dto.setOrderTime(job.getOrderTime());
        dto.setFinishTime(job.getFinishTime());
        dto.setTourName(job.getTourName());
        dto.setCourierId(job.getCourierId());
        dto.setCourierSid(job.getCourierSid());
        dto.setVehicleTypeId(job.getVehicleTypeId());
        dto.setTotalPrice(totalPrice != null ? totalPrice : job.getTotalPrice());
        dto.setExported(job.getExportTime() != null);

        Tour pickup = tours.stream().filter(tour -> Integer.valueOf(1).equals(tour.getSort())).findFirst().orElse(null);
        Tour delivery = tours.stream().filter(tour -> Integer.valueOf(2).equals(tour.getSort())).findFirst().orElse(null);
        dto.setCommissionNo(firstNonBlank(
                pickup != null ? pickup.getCommissionNo() : null,
                delivery != null ? delivery.getCommissionNo() : null
        ));
        dto.setPickupName(formatTourStop(pickup));
        dto.setDeliveryName(formatTourStop(delivery));

        Long resolvedCostCenterId = resolveCostCenterId(job, tours);
        dto.setCostCenterId(resolvedCostCenterId);
        CostCenter costCenter = resolvedCostCenterId != null ? costCenterById.get(resolvedCostCenterId) : null;
        dto.setCostCenterName(costCenter != null ? costCenter.getName() : "");
        if (costCenter != null) {
            dto.setCustomerId(costCenter.getCustomerId());
            dto.setCustomerName(customerNameById.getOrDefault(costCenter.getCustomerId(), ""));
        } else {
            dto.setCustomerName("");
        }
        return dto;
    }

    private Map<Long, BigDecimal> resolveJobListRevenueByJobId(List<Job> jobs) {
        if (!usePersistedTourserviceRevenueForJobList(jobs)) {
            return Map.of();
        }

        Map<Long, Map<Long, BigDecimal>> businessVolumeByJobId = jobTourServiceReadService.findBusinessVolumeByJobIds(
                jobs.stream().map(Job::getId).toList()
        );
        Map<Long, BigDecimal> result = new LinkedHashMap<>();
        for (Job job : jobs) {
            result.put(job.getId(), resolveJobListRevenue(job, businessVolumeByJobId.get(job.getId())));
        }
        return result;
    }

    private boolean usePersistedTourserviceRevenueForJobList(List<Job> jobs) {
        Long hqId = jobs.stream()
                .map(Job::getHeadquartersId)
                .filter(id -> id != null && id > 0)
                .findFirst()
                .orElse(null);
        return "1".equals(findParameterValueWithFallback(ParameterKeys.MASK_JOBLIST_TOTALPRICE, hqId));
    }

    private BigDecimal resolveJobListRevenue(Job job, Map<Long, BigDecimal> businessVolumeByCostCenterId) {
        if (businessVolumeByCostCenterId != null && !businessVolumeByCostCenterId.isEmpty()) {
            BigDecimal total = null;
            for (BigDecimal value : businessVolumeByCostCenterId.values()) {
                if (value == null) {
                    continue;
                }
                total = total == null ? value : total.add(value);
            }
            if (total != null) {
                return total;
            }
        }
        return job.getTotalPrice() != null ? job.getTotalPrice() : BigDecimal.ZERO;
    }

    private Long resolveCostCenterId(Job job, List<Tour> tours) {
        Long resolvedCostCenterId = job.getCostCenterPayerId();
        if (resolvedCostCenterId != null && resolvedCostCenterId > 0) {
            return resolvedCostCenterId;
        }
        Tour pickup = tours.stream()
                .filter(tour -> Integer.valueOf(1).equals(tour.getSort()))
                .findFirst()
                .orElse(null);
        if (pickup != null && pickup.getCostCenterId() != null && pickup.getCostCenterId() > 0) {
            return pickup.getCostCenterId();
        }
        if (job.getCostCenterRelatedId() != null && job.getCostCenterRelatedId() > 0) {
            return job.getCostCenterRelatedId();
        }
        return null;
    }

    private String resolveCustomerName(Customer customer) {
        return companyRepository.findById(Objects.requireNonNull(customer.getCompanyId()))
                .map(Company::getComp)
                .filter(name -> name != null && !name.isBlank())
                .orElse(customer.getEid());
    }

    private String formatTourStop(Tour tour) {
        if (tour == null) {
            return "";
        }
        return firstNonBlank(tour.getComp(), tour.getPerson(), "");
    }

    private BigDecimal safeSubtract(BigDecimal left, BigDecimal right) {
        BigDecimal normalizedLeft = left != null ? left : BigDecimal.ZERO;
        BigDecimal normalizedRight = right != null ? right : BigDecimal.ZERO;
        return normalizedLeft.subtract(normalizedRight);
    }

    private BigDecimal sumSpecificBusinessVolume(Map<Long, BigDecimal> businessVolumeByCostCenterId,
                                                 Set<Long> relevantCostCenterIds) {
        if (businessVolumeByCostCenterId == null || businessVolumeByCostCenterId.isEmpty()) {
            return null;
        }
        BigDecimal total = null;
        for (Map.Entry<Long, BigDecimal> entry : businessVolumeByCostCenterId.entrySet()) {
            Long costCenterId = entry.getKey();
            BigDecimal value = entry.getValue();
            if (costCenterId == null || costCenterId <= 0 || value == null) {
                continue;
            }
            if (relevantCostCenterIds != null && !relevantCostCenterIds.isEmpty() && !relevantCostCenterIds.contains(costCenterId)) {
                continue;
            }
            total = total == null ? value : total.add(value);
        }
        return total;
    }

    private List<Long> resolveMatchingInvoiceCostCenterIds(Set<Long> relevantCostCenterIds,
                                                           Map<Long, BigDecimal> businessVolumeByCostCenterId,
                                                           List<JobTourServiceRowDto> rows) {
        LinkedHashSet<Long> result = new LinkedHashSet<>();
        if (businessVolumeByCostCenterId != null) {
            for (Map.Entry<Long, BigDecimal> entry : businessVolumeByCostCenterId.entrySet()) {
                Long costCenterId = entry.getKey();
                BigDecimal value = entry.getValue();
                if (costCenterId == null || costCenterId <= 0 || value == null) {
                    continue;
                }
                if (relevantCostCenterIds != null && !relevantCostCenterIds.isEmpty() && !relevantCostCenterIds.contains(costCenterId)) {
                    continue;
                }
                result.add(costCenterId);
            }
        }
        if (rows != null) {
            for (JobTourServiceRowDto row : rows) {
                Long costCenterId = row.getCostCenterId();
                if (costCenterId == null || costCenterId <= 0) {
                    continue;
                }
                if (relevantCostCenterIds != null && !relevantCostCenterIds.isEmpty() && !relevantCostCenterIds.contains(costCenterId)) {
                    continue;
                }
                result.add(costCenterId);
            }
        }
        return new ArrayList<>(result);
    }

    private List<JobTourServiceRowDto> filterRowsByCostCenter(List<JobTourServiceRowDto> rows, Long costCenterId) {
        if (rows == null || rows.isEmpty() || costCenterId == null) {
            return List.of();
        }
        return rows.stream()
                .filter(row -> costCenterId.equals(row.getCostCenterId()))
                .toList();
    }

    private String formatInvoiceServiceRows(List<JobTourServiceRowDto> rows,
                                            InvoiceServiceDisplayOptions displayOptions) {
        if (rows == null || rows.isEmpty()) {
            return "";
        }
        StringBuilder details = new StringBuilder();
        for (JobTourServiceRowDto row : rows) {
            BigDecimal businessVolume = row.getBusinessVolume() != null ? row.getBusinessVolume() : row.getPrice();
            if (businessVolume == null || businessVolume.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }
            if (details.length() > 0) {
                details.append('\n');
            }
            details.append(firstNonBlank(row.getServiceName(), "Leistung"));
            if (displayOptions.includeServiceType
                    && row.getServiceTypeName() != null
                    && !row.getServiceTypeName().isBlank()) {
                details.append(" / ").append(row.getServiceTypeName());
            }
            details.append(": ").append(businessVolume.toPlainString());
            if (displayOptions.includeServicePrice && row.getPrice() != null) {
                details.append(" [Preis ").append(row.getPrice().toPlainString()).append(']');
            }
            if (displayOptions.includeDiscount
                    && row.getDiscount() != null
                    && row.getDiscount().compareTo(BigDecimal.ZERO) != 0) {
                details.append(" [Rabatt ").append(row.getDiscount().toPlainString()).append("%]");
            }
        }
        return details.toString();
    }

    private String resolveInvoiceCostCenterName(Long explicitCostCenterId,
                                                List<Long> matchingCostCenterIds,
                                                Long fallbackResolvedCostCenterId,
                                                Map<Long, CostCenter> costCenterById) {
        if (explicitCostCenterId != null) {
            return resolveCostCenterName(explicitCostCenterId, costCenterById);
        }
        if (matchingCostCenterIds != null && !matchingCostCenterIds.isEmpty()) {
            if (matchingCostCenterIds.size() == 1) {
                return resolveCostCenterName(matchingCostCenterIds.get(0), costCenterById);
            }
            List<String> names = new ArrayList<>();
            for (Long costCenterId : matchingCostCenterIds) {
                String name = resolveCostCenterName(costCenterId, costCenterById);
                if (!name.isBlank()) {
                    names.add(name);
                }
            }
            if (!names.isEmpty()) {
                return String.join(" / ", names);
            }
            return "Mehrere Kostenstellen";
        }
        return resolveCostCenterName(fallbackResolvedCostCenterId, costCenterById);
    }

    private String resolveCostCenterName(Long costCenterId, Map<Long, CostCenter> costCenterById) {
        if (costCenterId == null || costCenterById == null) {
            return "";
        }
        CostCenter costCenter = costCenterById.get(costCenterId);
        return costCenter != null && costCenter.getName() != null ? costCenter.getName() : "";
    }

    private String firstNonBlank(String... values) {
        for (String value : values) {
            if (value != null && !value.isBlank()) {
                return value;
            }
        }
        return "";
    }

    private List<Job> loadJobs(Long hqId, LocalDate fromDate, LocalDate toDate) {
        if (fromDate != null && toDate != null) {
            return new ArrayList<>(jobRepository.findByOrderTimeBetweenAndHq(
                    fromDate.atStartOfDay(), toDate.atTime(23, 59, 59), hqId));
        }
        return new ArrayList<>(jobRepository.findByHeadquartersId(hqId));
    }

    private List<Job> loadJobsForCostCenters(Long hqId,
                                             List<Long> accessibleCostCenterIds,
                                             LocalDate fromDate,
                                             LocalDate toDate) {
        if (accessibleCostCenterIds == null || accessibleCostCenterIds.isEmpty()) {
            return List.of();
        }

        return new ArrayList<>(jobRepository.findByPayerCostCenters(accessibleCostCenterIds).stream()
                .filter(job -> hqId.equals(job.getHeadquartersId()))
                .filter(job -> {
                    if (fromDate == null || toDate == null || job.getOrderTime() == null) {
                        return true;
                    }
                    LocalDate jobDate = job.getOrderTime().toLocalDate();
                    return !jobDate.isBefore(fromDate) && !jobDate.isAfter(toDate);
                })
                .toList());
    }

    private BigDecimal sumJobs(List<Job> jobs, boolean revenue) {
        BigDecimal sum = BigDecimal.ZERO;
        for (Job job : jobs) {
            BigDecimal value = revenue ? job.getTotalPrice() : job.getCourierPrice();
            if (value != null) {
                sum = sum.add(value);
            }
        }
        return sum;
    }

    private BigDecimal sumInvoiceRevenue(List<Job> jobs, Map<Long, BigDecimal> invoiceRevenueByJobId) {
        BigDecimal sum = BigDecimal.ZERO;
        for (Job job : jobs) {
            BigDecimal value = invoiceRevenueByJobId.get(job.getId());
            if (value == null) {
                value = job.getTotalPrice();
            }
            if (value != null) {
                sum = sum.add(value);
            }
        }
        return sum;
    }

    private String findParameterValueWithFallback(String key, Long hqId) {
        String value = findParameterValue(key, hqId);
        if (value != null && !value.isBlank()) {
            return value;
        }
        if (hqId != null && hqId > 0) {
            return findParameterValue(key, 0L);
        }
        return value != null ? value : "";
    }

    private String findParameterValue(String key, Long hqId) {
        if (key == null || hqId == null) {
            return "";
        }
        return parameterRepository.findByKeyAndHqAndEmployee(key, hqId, 0L)
                .map(Parameter::getValue)
                .orElse("");
    }

    private String joinName(String firstname, String name) {
        return (firstname != null ? firstname : "") +
                ((firstname != null && !firstname.isBlank() && name != null && !name.isBlank()) ? " " : "") +
                (name != null ? name : "");
    }

    private record InvoicePresentation(Long costCenterId,
                                       String costCenterName,
                                       BigDecimal revenue,
                                       String serviceDetails) {
    }

    private record InvoiceServiceDisplayOptions(boolean includeServiceType,
                                                boolean includeDiscount,
                                                boolean includeServicePrice) {
    }
}
