package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
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.entity.Company;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Job;
import de.votian.services.entity.Parameter;
import de.votian.services.entity.Tour;
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 jakarta.persistence.EntityManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@SuppressWarnings("null")
class ViewDataServiceTest {

    private CustomerRepository customerRepository;
    private CostCenterRepository costCenterRepository;
    private CompanyRepository companyRepository;
    private TourRepository tourRepository;
    private JobRepository jobRepository;
    private ParameterRepository parameterRepository;
    private StubJobTourServiceReadService jobTourServiceReadService;
    private ViewDataService viewDataService;

    @BeforeEach
    void setUp() {
        customerRepository = mock(CustomerRepository.class);
        costCenterRepository = mock(CostCenterRepository.class);
        companyRepository = mock(CompanyRepository.class);
        tourRepository = mock(TourRepository.class);
        jobRepository = mock(JobRepository.class);
        parameterRepository = mock(ParameterRepository.class);
        jobTourServiceReadService = new StubJobTourServiceReadService();

        viewDataService = new ViewDataService(
                customerRepository,
                costCenterRepository,
                mock(CourierRepository.class),
                mock(EmployeeRepository.class),
                companyRepository,
                mock(UserRepository.class),
                mock(AddressRepository.class),
                tourRepository,
                mock(HeadquartersRepository.class),
                parameterRepository,
                jobRepository,
                jobTourServiceReadService,
                mock(ServiceEntityRepository.class),
                mock(ServiceTypeRepository.class),
                mock(ServiceHistoryRepository.class)
        );
    }

    @Test
    void buildInvoiceSummaryUsesPersistedTourserviceRevenueForInvoiceableJobs() {
        Job invoiceableJob = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("60.00"),
                LocalDateTime.of(2026, 3, 5, 9, 0), LocalDateTime.of(2026, 3, 5, 10, 0), 9, "0");
        Job openJob = job(2L, 1L, 11L, new BigDecimal("999.00"), new BigDecimal("1.00"),
                LocalDateTime.of(2026, 3, 6, 9, 0), null, 1, "0");
        when(jobRepository.findByOrderTimeBetweenAndHq(
                LocalDate.of(2026, 3, 1).atStartOfDay(),
                LocalDate.of(2026, 3, 31).atTime(23, 59, 59),
                1L
        )).thenReturn(List.of(invoiceableJob, openJob));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of());

        jobTourServiceReadService.revenueByJobId = Map.of(
                1L, Map.of(11L, new BigDecimal("80.00"))
        );

        InvoiceSummaryDto result = viewDataService.buildInvoiceSummary(
                1L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        );

        assertThat(result.getTotalJobs()).isEqualTo(2);
        assertThat(result.getBillableJobs()).isEqualTo(1);
        assertThat(result.getTotalRevenue()).isEqualByComparingTo("80.00");
        assertThat(result.getTotalCourierCosts()).isEqualByComparingTo("60.00");
        assertThat(result.getContributionMargin()).isEqualByComparingTo("20.00");
    }

    @Test
    void buildInvoiceSummarySumsPersistedSplitRevenueAcrossAllCostCenters() {
        Job invoiceableJob = job(1L, 1L, 0L, new BigDecimal("999.00"), new BigDecimal("60.00"),
                LocalDateTime.of(2026, 3, 5, 9, 0), LocalDateTime.of(2026, 3, 5, 10, 0), 9, "0");
        when(jobRepository.findByOrderTimeBetweenAndHq(
                LocalDate.of(2026, 3, 1).atStartOfDay(),
                LocalDate.of(2026, 3, 31).atTime(23, 59, 59),
                1L
        )).thenReturn(List.of(invoiceableJob));

        Map<Long, BigDecimal> splitRevenue = new LinkedHashMap<>();
        splitRevenue.put(11L, new BigDecimal("30.00"));
        splitRevenue.put(12L, new BigDecimal("50.00"));
        jobTourServiceReadService.revenueByJobId = Map.of(1L, splitRevenue);

        InvoiceSummaryDto result = viewDataService.buildInvoiceSummary(
                1L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        );

        assertThat(result.getBillableJobs()).isEqualTo(1);
        assertThat(result.getTotalRevenue()).isEqualByComparingTo("80.00");
        assertThat(result.getContributionMargin()).isEqualByComparingTo("20.00");
    }

    @Test
    void findInvoiceItemsUsesResolvedTourserviceRevenueAndFallsBackToGlobalRows() {
        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter = new CostCenter();
        costCenter.setId(11L);
        costCenter.setCustomerId(5L);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName("KST 11");
        costCenter.setPath("001");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter));

        Job exactCostCenterJob = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        exactCostCenterJob.setCourierSid("CR-1");
        Job globalFallbackJob = job(2L, 1L, 11L, new BigDecimal("120.00"), new BigDecimal("30.00"),
                LocalDateTime.of(2026, 3, 6, 8, 0), LocalDateTime.of(2026, 3, 6, 9, 0), 9, "0");
        globalFallbackJob.setCourierSid("CR-2");
        when(jobRepository.findByPayerCostCenters(List.of(11L))).thenReturn(List.of(exactCostCenterJob, globalFallbackJob));

        Tour pickup1 = new Tour();
        pickup1.setJobId(1L);
        pickup1.setSort(1);
        pickup1.setComp("Abholung A");
        pickup1.setCommissionNo("KOMM-1");
        Tour delivery1 = new Tour();
        delivery1.setJobId(1L);
        delivery1.setSort(2);
        delivery1.setComp("Zustellung A");
        Tour pickup2 = new Tour();
        pickup2.setJobId(2L);
        pickup2.setSort(1);
        pickup2.setComp("Abholung B");
        pickup2.setCommissionNo("KOMM-2");
        Tour delivery2 = new Tour();
        delivery2.setJobId(2L);
        delivery2.setSort(2);
        delivery2.setComp("Zustellung B");
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L, 2L)))
                .thenReturn(List.of(pickup1, delivery1, pickup2, delivery2));

        jobTourServiceReadService.revenueByJobId = new LinkedHashMap<>();
        jobTourServiceReadService.revenueByJobId.put(1L, Map.of(11L, new BigDecimal("42.50")));
        jobTourServiceReadService.revenueByJobId.put(2L, Map.of(0L, new BigDecimal("55.00")));

        List<InvoiceJobRowDto> result = viewDataService.findInvoiceItems(
                1L,
                5L,
                null,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        );

        assertThat(result).hasSize(2);
        assertThat(result)
                .extracting(
                        InvoiceJobRowDto::getJobId,
                        InvoiceJobRowDto::getTotalPrice,
                        InvoiceJobRowDto::getMargin,
                        InvoiceJobRowDto::getCostCenterName,
                        InvoiceJobRowDto::getPickupName,
                        InvoiceJobRowDto::getDeliveryName
                )
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple(
                                1L,
                                new BigDecimal("42.50"),
                                new BigDecimal("17.50"),
                                "KST 11",
                                "Abholung A",
                                "Zustellung A"
                        ),
                        org.assertj.core.groups.Tuple.tuple(
                                2L,
                                new BigDecimal("55.00"),
                                new BigDecimal("25.00"),
                                "KST 11",
                                "Abholung B",
                                "Zustellung B"
                        )
                );
    }

    @Test
    void findInvoiceItemsIncludesPersistedServiceDetailsWhenCustomerMaskIsEnabled() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_INVOICE_SHOW_SERVICES + "5", 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter = new CostCenter();
        costCenter.setId(11L);
        costCenter.setCustomerId(5L);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName("KST 11");
        costCenter.setPath("001");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter));

        Job job = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByPayerCostCenters(List.of(11L))).thenReturn(List.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of());

        jobTourServiceReadService.revenueByJobId = Map.of(
                1L, Map.of(11L, new BigDecimal("42.50"))
        );
        jobTourServiceReadService.rowsByJobId = Map.of(
                1L, List.of(
                        serviceRow(11L, "Fixpreis", "", new BigDecimal("40.00"), BigDecimal.ZERO),
                        serviceRow(11L, "Servicepreis", "Express", new BigDecimal("5.00"), BigDecimal.ZERO),
                        serviceRow(0L, "Fixpreis", "", new BigDecimal("999.00"), BigDecimal.ZERO)
                )
        );

        List<InvoiceJobRowDto> result = viewDataService.findInvoiceItems(
                1L,
                5L,
                null,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        );

        assertThat(result).singleElement().satisfies(row -> {
            assertThat(row.getServiceDetails()).isEqualTo("Fixpreis: 40.00\nServicepreis: 5.00");
            assertThat(row.getTotalPrice()).isEqualByComparingTo("42.50");
        });
    }

    @Test
    void findInvoiceItemsFormatsPersistedServiceDetailsUsingLegacyPrintParameters() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_INVOICE_SHOW_SERVICES + "5", 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.INV_PRINT_METATYPE_SERVICE, 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.INV_PRINT_DISCOUNT, 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.INV_PRINT_SRVPRICE, 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter = new CostCenter();
        costCenter.setId(11L);
        costCenter.setCustomerId(5L);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName("KST 11");
        costCenter.setPath("001");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter));

        Job job = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByPayerCostCenters(List.of(11L))).thenReturn(List.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of());

        jobTourServiceReadService.revenueByJobId = Map.of(
                1L, Map.of(11L, new BigDecimal("44.95"))
        );
        jobTourServiceReadService.rowsByJobId = Map.of(
                1L, List.of(
                        serviceRow(11L, "Servicepreis", "Express", new BigDecimal("5.50"), new BigDecimal("10.00"))
                )
        );

        List<InvoiceJobRowDto> result = viewDataService.findInvoiceItems(
                1L,
                5L,
                null,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        );

        assertThat(result).singleElement().satisfies(row ->
                assertThat(row.getServiceDetails()).isEqualTo("Servicepreis / Express: 4.95 [Preis 5.50] [Rabatt 10.00%]"));
    }

    @Test
    void findInvoiceCustomersSumsPersistedSplitRevenueAcrossVisibleCostCenters() {
        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        customer.setEid("KD-5");
        when(customerRepository.findByHeadquartersId(1L)).thenReturn(List.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter11 = visibleCostCenter(11L, 5L, "KST 11", "001");
        CostCenter costCenter12 = visibleCostCenter(12L, 5L, "KST 12", "002");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter11, costCenter12));

        Job splitJob = job(1L, 1L, 0L, new BigDecimal("999.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByPayerCostCenters(List.of(11L, 12L))).thenReturn(List.of(splitJob));

        Map<Long, BigDecimal> splitRevenue = new LinkedHashMap<>();
        splitRevenue.put(11L, new BigDecimal("40.00"));
        splitRevenue.put(12L, new BigDecimal("15.00"));
        jobTourServiceReadService.revenueByJobId = Map.of(1L, splitRevenue);

        assertThat(viewDataService.findInvoiceCustomers(
                1L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        )).singleElement().satisfies(row -> {
            assertThat(row.getCustomerName()).isEqualTo("Musterkunde");
            assertThat(row.getInvoiceableJobs()).isEqualTo(1);
            assertThat(row.getTotalRevenue()).isEqualByComparingTo("55.00");
        });
    }

    @Test
    void findInvoiceItemsUsesSelectedSplitCostCenterForRevenueAndServiceDetails() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_INVOICE_SHOW_SERVICES + "5", 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter11 = visibleCostCenter(11L, 5L, "KST 11", "001");
        CostCenter costCenter12 = visibleCostCenter(12L, 5L, "KST 12", "002");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter11, costCenter12));

        Job splitJob = job(1L, 1L, 0L, new BigDecimal("999.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByPayerCostCenters(List.of(12L))).thenReturn(List.of(splitJob));

        Tour pickup = new Tour();
        pickup.setJobId(1L);
        pickup.setSort(1);
        pickup.setComp("Abholung");
        pickup.setCommissionNo("KOMM-1");
        pickup.setCostCenterId(11L);
        Tour delivery = new Tour();
        delivery.setJobId(1L);
        delivery.setSort(2);
        delivery.setComp("Zustellung");
        delivery.setCostCenterId(12L);
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of(pickup, delivery));

        Map<Long, BigDecimal> splitRevenue = new LinkedHashMap<>();
        splitRevenue.put(11L, new BigDecimal("40.00"));
        splitRevenue.put(12L, new BigDecimal("15.00"));
        jobTourServiceReadService.revenueByJobId = Map.of(1L, splitRevenue);
        jobTourServiceReadService.rowsByJobId = Map.of(
                1L, List.of(
                        serviceRow(11L, "Fixpreis", "", new BigDecimal("40.00"), BigDecimal.ZERO),
                        serviceRow(12L, "Fixpreis", "", new BigDecimal("15.00"), BigDecimal.ZERO)
                )
        );

        assertThat(viewDataService.findInvoiceItems(
                1L,
                5L,
                12L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        )).singleElement().satisfies(row -> {
            assertThat(row.getCostCenterId()).isEqualTo(12L);
            assertThat(row.getCostCenterName()).isEqualTo("KST 12");
            assertThat(row.getTotalPrice()).isEqualByComparingTo("15.00");
            assertThat(row.getServiceDetails()).isEqualTo("Fixpreis: 15.00");
        });
    }

    @Test
    void findInvoiceItemsAggregatesSplitRevenueAcrossVisibleCostCentersAndGroupsServiceDetails() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_INVOICE_SHOW_SERVICES + "5", 1L, 0L))
                .thenReturn(Optional.of(parameter("1")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter11 = visibleCostCenter(11L, 5L, "KST 11", "001");
        CostCenter costCenter12 = visibleCostCenter(12L, 5L, "KST 12", "002");
        when(costCenterRepository.findVisibleByCustomerId(5L)).thenReturn(List.of(costCenter11, costCenter12));

        Job splitJob = job(1L, 1L, 0L, new BigDecimal("999.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByPayerCostCenters(List.of(11L, 12L))).thenReturn(List.of(splitJob));

        Tour pickup = new Tour();
        pickup.setJobId(1L);
        pickup.setSort(1);
        pickup.setComp("Abholung");
        pickup.setCommissionNo("KOMM-1");
        pickup.setCostCenterId(11L);
        Tour delivery = new Tour();
        delivery.setJobId(1L);
        delivery.setSort(2);
        delivery.setComp("Zustellung");
        delivery.setCostCenterId(12L);
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of(pickup, delivery));

        Map<Long, BigDecimal> splitRevenue = new LinkedHashMap<>();
        splitRevenue.put(11L, new BigDecimal("40.00"));
        splitRevenue.put(12L, new BigDecimal("15.00"));
        jobTourServiceReadService.revenueByJobId = Map.of(1L, splitRevenue);
        jobTourServiceReadService.rowsByJobId = Map.of(
                1L, List.of(
                        serviceRow(11L, "Fixpreis", "", new BigDecimal("40.00"), BigDecimal.ZERO),
                        serviceRow(12L, "Servicepreis", "", new BigDecimal("15.00"), BigDecimal.ZERO)
                )
        );

        assertThat(viewDataService.findInvoiceItems(
                1L,
                5L,
                null,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31)
        )).singleElement().satisfies(row -> {
            assertThat(row.getCostCenterId()).isNull();
            assertThat(row.getCostCenterName()).isEqualTo("KST 11 / KST 12");
            assertThat(row.getTotalPrice()).isEqualByComparingTo("55.00");
            assertThat(row.getServiceDetails()).isEqualTo("KST 11:\nFixpreis: 40.00\n\nKST 12:\nServicepreis: 15.00");
        });
    }

    @Test
    void findJobListRowsUsesPersistedTourserviceRevenueWhenConfiguredViaGlobalFallback() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_JOBLIST_TOTALPRICE, 1L, 0L))
                .thenReturn(Optional.empty());
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_JOBLIST_TOTALPRICE, 0L, 0L))
                .thenReturn(Optional.of(parameter("1")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter = new CostCenter();
        costCenter.setId(11L);
        costCenter.setCustomerId(5L);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName("KST 11");
        costCenter.setPath("001");
        when(costCenterRepository.findAllById(List.of(11L))).thenReturn(List.of(costCenter));

        Job persistedRevenueJob = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        persistedRevenueJob.setCourierSid("CR-1");
        persistedRevenueJob.setTourName("Tour A");
        Job fallbackJob = job(2L, 1L, 11L, new BigDecimal("99.00"), new BigDecimal("30.00"),
                LocalDateTime.of(2026, 3, 6, 8, 0), LocalDateTime.of(2026, 3, 6, 9, 0), 9, "0");
        fallbackJob.setCourierSid("CR-2");
        fallbackJob.setTourName("Tour B");

        when(jobRepository.findByOrderTimeBetweenAndHq(
                LocalDate.of(2026, 3, 1).atStartOfDay(),
                LocalDate.of(2026, 3, 31).atTime(23, 59, 59),
                1L
        )).thenReturn(List.of(persistedRevenueJob, fallbackJob));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L, 2L))).thenReturn(List.of());

        Map<Long, BigDecimal> revenueByCostCenter = new LinkedHashMap<>();
        revenueByCostCenter.put(11L, new BigDecimal("42.50"));
        revenueByCostCenter.put(12L, new BigDecimal("7.50"));
        jobTourServiceReadService.revenueByJobId = new LinkedHashMap<>();
        jobTourServiceReadService.revenueByJobId.put(1L, revenueByCostCenter);

        List<JobListRowDto> result = viewDataService.findJobListRows(
                1L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31),
                null,
                null,
                null,
                null,
                null
        );

        assertThat(result)
                .extracting(JobListRowDto::getId, JobListRowDto::getTotalPrice, JobListRowDto::getCustomerName, JobListRowDto::getCostCenterName)
                .contains(
                        org.assertj.core.groups.Tuple.tuple(1L, new BigDecimal("50.00"), "Musterkunde", "KST 11"),
                        org.assertj.core.groups.Tuple.tuple(2L, new BigDecimal("99.00"), "Musterkunde", "KST 11")
                );
    }

    @Test
    void findJobListRowsKeepsStoredJobTotalWhenMaskIsDisabled() {
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_JOBLIST_TOTALPRICE, 1L, 0L))
                .thenReturn(Optional.of(parameter("0")));

        Customer customer = new Customer();
        customer.setId(5L);
        customer.setCompanyId(7L);
        when(customerRepository.findById(5L)).thenReturn(Optional.of(customer));

        Company company = new Company();
        company.setId(7L);
        company.setComp("Musterkunde");
        when(companyRepository.findById(7L)).thenReturn(Optional.of(company));

        CostCenter costCenter = new CostCenter();
        costCenter.setId(11L);
        costCenter.setCustomerId(5L);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName("KST 11");
        costCenter.setPath("001");
        when(costCenterRepository.findAllById(List.of(11L))).thenReturn(List.of(costCenter));

        Job job = job(1L, 1L, 11L, new BigDecimal("100.00"), new BigDecimal("25.00"),
                LocalDateTime.of(2026, 3, 5, 8, 0), LocalDateTime.of(2026, 3, 5, 9, 0), 9, "0");
        when(jobRepository.findByOrderTimeBetweenAndHq(
                LocalDate.of(2026, 3, 1).atStartOfDay(),
                LocalDate.of(2026, 3, 31).atTime(23, 59, 59),
                1L
        )).thenReturn(List.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of());

        jobTourServiceReadService.revenueByJobId = Map.of(
                1L, Map.of(11L, new BigDecimal("50.00"))
        );

        List<JobListRowDto> result = viewDataService.findJobListRows(
                1L,
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 31),
                null,
                null,
                null,
                null,
                null
        );

        assertThat(result).singleElement().extracting(JobListRowDto::getTotalPrice).isEqualTo(new BigDecimal("100.00"));
    }

    private Job job(Long id,
                    Long hqId,
                    Long payerCostCenterId,
                    BigDecimal totalPrice,
                    BigDecimal courierPrice,
                    LocalDateTime orderTime,
                    LocalDateTime finishTime,
                    Integer status,
                    String storno) {
        Job job = new Job();
        job.setId(id);
        job.setHeadquartersId(hqId);
        job.setCostCenterPayerId(payerCostCenterId);
        job.setTotalPrice(totalPrice);
        job.setCourierPrice(courierPrice);
        job.setOrderTime(orderTime);
        job.setFinishTime(finishTime);
        job.setStatus(status);
        job.setStorno(storno);
        return job;
    }

    private Parameter parameter(String value) {
        Parameter parameter = new Parameter();
        parameter.setValue(value);
        return parameter;
    }

    private CostCenter visibleCostCenter(Long id, Long customerId, String name, String path) {
        CostCenter costCenter = new CostCenter();
        costCenter.setId(id);
        costCenter.setCustomerId(customerId);
        costCenter.setVisible("1");
        costCenter.setIsExtern("0");
        costCenter.setName(name);
        costCenter.setPath(path);
        return costCenter;
    }

    private JobTourServiceRowDto serviceRow(Long costCenterId,
                                            String serviceName,
                                            String serviceTypeName,
                                            BigDecimal price,
                                            BigDecimal discount) {
        JobTourServiceRowDto row = new JobTourServiceRowDto();
        row.setCostCenterId(costCenterId);
        row.setServiceName(serviceName);
        row.setServiceTypeName(serviceTypeName);
        row.setPrice(price);
        row.setDiscount(discount);
        BigDecimal factor = BigDecimal.ONE.subtract(
                discount.divide(new BigDecimal("100"), 6, java.math.RoundingMode.HALF_UP)
        );
        row.setBusinessVolume(price.multiply(factor).setScale(2, java.math.RoundingMode.HALF_UP));
        return row;
    }

    private static final class StubJobTourServiceReadService extends JobTourServiceReadService {

        private Map<Long, Map<Long, BigDecimal>> revenueByJobId = Map.of();
        private Map<Long, List<JobTourServiceRowDto>> rowsByJobId = Map.of();

        private StubJobTourServiceReadService() {
            super(unsupportedEntityManager());
        }

        @Override
        public Map<Long, Map<Long, BigDecimal>> findBusinessVolumeByJobIds(List<Long> jobIds) {
            return revenueByJobId;
        }

        @Override
        public Map<Long, List<JobTourServiceRowDto>> findRowsByJobIds(List<Long> jobIds) {
            return rowsByJobId;
        }
    }

    private static EntityManager unsupportedEntityManager() {
        return (EntityManager) Proxy.newProxyInstance(
                EntityManager.class.getClassLoader(),
                new Class[]{EntityManager.class},
                (proxy, method, args) -> {
                    if ("toString".equals(method.getName())) {
                        return "UnsupportedEntityManager";
                    }
                    if ("hashCode".equals(method.getName())) {
                        return System.identityHashCode(proxy);
                    }
                    if ("equals".equals(method.getName())) {
                        return proxy == args[0];
                    }
                    throw new UnsupportedOperationException("EntityManager should not be used in this test.");
                }
        );
    }
}
