package de.votian.services.controller;

import de.votian.services.dto.JobAcceptanceProtocolDto;
import de.votian.services.dto.InvoiceSummaryDto;
import de.votian.services.dto.JobListRowDto;
import de.votian.services.dto.JobPricingRequest;
import de.votian.services.dto.JobPricingResultDto;
import de.votian.services.dto.JobTourServiceRowDto;
import de.votian.services.dto.TourDetailDto;
import de.votian.services.dto.UserSessionDto;
import de.votian.services.entity.*;
import de.votian.services.repository.AddressRepository;
import de.votian.services.security.AccessControlService;
import de.votian.services.service.JobBatchService;
import de.votian.services.service.JobAcceptanceProtocolService;
import de.votian.services.service.JobListExportService;
import de.votian.services.service.JobPricingService;
import de.votian.services.service.JobService;
import de.votian.services.service.JobTourServiceReadService;
import de.votian.services.service.ViewDataService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@RestController
@RequestMapping("/api/jobs")
public class JobController {

    private final JobService jobService;
    private final JobBatchService jobBatchService;
    private final JobAcceptanceProtocolService jobAcceptanceProtocolService;
    private final JobPricingService jobPricingService;
    private final JobListExportService jobListExportService;
    private final JobTourServiceReadService jobTourServiceReadService;
    private final ViewDataService viewDataService;
    private final AddressRepository addressRepository;
    private final AccessControlService accessControlService;

    public JobController(JobService jobService,
                         JobBatchService jobBatchService,
                         JobAcceptanceProtocolService jobAcceptanceProtocolService,
                         JobPricingService jobPricingService,
                         JobListExportService jobListExportService,
                         JobTourServiceReadService jobTourServiceReadService,
                         ViewDataService viewDataService,
                         AddressRepository addressRepository,
                         AccessControlService accessControlService) {
        this.jobService = jobService;
        this.jobBatchService = jobBatchService;
        this.jobAcceptanceProtocolService = jobAcceptanceProtocolService;
        this.jobPricingService = jobPricingService;
        this.jobListExportService = jobListExportService;
        this.jobTourServiceReadService = jobTourServiceReadService;
        this.viewDataService = viewDataService;
        this.addressRepository = addressRepository;
        this.accessControlService = accessControlService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<Job> findById(@PathVariable Long id, Authentication authentication) {
        return jobService.findById(id)
                .map(job -> canAccessJob(authentication, job)
                        ? ResponseEntity.ok(job)
                        : ResponseEntity.status(HttpStatus.FORBIDDEN).<Job>build())
                .orElse(ResponseEntity.notFound().<Job>build());
    }

    @GetMapping("/hq/{hqId}")
    public ResponseEntity<List<Job>> findByHeadquarters(@PathVariable Long hqId, Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canViewJobs(user) || !accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.findByHeadquarters(hqId));
    }

    @GetMapping("/list")
    public ResponseEntity<List<JobListRowDto>> findForList(
            @RequestParam Long hqId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            @RequestParam(required = false) String term,
            @RequestParam(required = false) Integer status,
            @RequestParam(required = false) Long costCenterId,
            @RequestParam(required = false) String courierSid,
            @RequestParam(required = false) Boolean exported,
            Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canViewJobs(user) || accessControlService.isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isCustomerUser(user)) {
            return ResponseEntity.ok(viewDataService.findJobListRowsForCostCenters(
                    user.getHeadquartersId(),
                    user.getAccessibleCostCenterIds(),
                    from,
                    to,
                    term,
                    status,
                    costCenterId,
                    courierSid,
                    exported
            ));
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findJobListRows(
                hqId,
                from,
                to,
                term,
                status,
                costCenterId,
                courierSid,
                exported
        ));
    }

    @GetMapping("/export/csv")
    public ResponseEntity<byte[]> exportListCsv(
            @RequestParam Long hqId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            @RequestParam(required = false) String term,
            @RequestParam(required = false) Integer status,
            @RequestParam(required = false) Long costCenterId,
            @RequestParam(required = false) String courierSid,
            @RequestParam(required = false) Boolean exported,
            Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canExportJobs(user) || accessControlService.isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        List<JobListRowDto> rows = loadJobListRowsForExport(
                user,
                hqId,
                from,
                to,
                term,
                status,
                costCenterId,
                courierSid,
                exported
        );
        if (rows == null) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return fileResponse(
                buildJobListFilename(from, to),
                "text/csv; charset=UTF-8",
                jobListExportService.createCsv("Auftragsliste", rows)
        );
    }

    @GetMapping("/filter-costcenters")
    public ResponseEntity<List<CostCenter>> getFilterCostCenters(@RequestParam Long hqId, Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canViewJobs(user) || accessControlService.isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (!isCustomerUser(user) && !accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findJobFilterCostCenters(
                isCustomerUser(user) ? user.getHeadquartersId() : hqId,
                isCustomerUser(user) ? user.getAccessibleCostCenterIds() : null
        ));
    }

    @GetMapping("/daterange")
    public ResponseEntity<List<Job>> findByDateRange(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to,
            @RequestParam Long hqId,
            Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (isCourierUser(user)) {
            return ResponseEntity.ok(jobService.findByCourierId(user.getCourierId()).stream()
                    .filter(job -> matchesDateRange(job.getOrderTime(), from, to))
                    .toList());
        }
        if (!accessControlService.canViewJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isCustomerUser(user)) {
            return ResponseEntity.ok(jobService.findByPayerCostCenters(user.getAccessibleCostCenterIds()).stream()
                    .filter(job -> matchesDateRange(job.getOrderTime(), from, to))
                    .filter(job -> user.getHeadquartersId() == null || user.getHeadquartersId().equals(job.getHeadquartersId()))
                    .toList());
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.findByOrderTimeBetween(from, to, hqId));
    }

    @GetMapping("/commission")
    public ResponseEntity<List<Job>> findByCommissionNo(@RequestParam String commNo,
                                                        @RequestParam Long customerId,
                                                        Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canViewJobs(user) || !accessControlService.canAccessCustomer(user, customerId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.findByCommissionNoAndCustomer(commNo, customerId));
    }

    @GetMapping("/today")
    public ResponseEntity<List<Job>> findTodaysJobs(Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (isCourierUser(user)) {
            return ResponseEntity.ok(jobService.findByCourierId(user.getCourierId()).stream()
                    .filter(job -> matchesDateRange(job.getOrderTime(),
                            java.time.LocalDate.now().atStartOfDay(),
                            java.time.LocalDate.now().atTime(23, 59, 59)))
                    .toList());
        }
        if (!accessControlService.canViewJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isCustomerUser(user)) {
            return ResponseEntity.ok(jobService.findByPayerCostCenters(user.getAccessibleCostCenterIds()).stream()
                    .filter(job -> matchesDateRange(job.getOrderTime(),
                            java.time.LocalDate.now().atStartOfDay(),
                            java.time.LocalDate.now().atTime(23, 59, 59)))
                    .filter(job -> user.getHeadquartersId() == null || user.getHeadquartersId().equals(job.getHeadquartersId()))
                    .toList());
        }
        return ResponseEntity.ok(jobService.findTodaysJobs());
    }

    @PostMapping
    public ResponseEntity<Job> create(@RequestBody JobCreateRequest request, Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canManageJobs(user) || isCourierUser(user) || !requestWithinCustomerScope(user, request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isHeadquartersUser(user) && request.job != null && !accessControlService.canAccessHeadquarters(user, request.job.getHeadquartersId())) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (request.job != null && user != null && user.getHeadquartersId() != null && !isHeadquartersUser(user)) {
            request.job.setHeadquartersId(user.getHeadquartersId());
        }
        sanitizeLonghaulUpdate(user, request != null ? request.job : null);
        Job created = jobService.createJob(
                request.job,
                mapTours(null, request.tours),
                request.articles,
                request.prices
        );
        return ResponseEntity.ok(created);
    }

    @PostMapping("/batch")
    public ResponseEntity<List<Job>> createBatch(@RequestBody BatchCreateRequest request, Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canBatchJobs(user)
                || isCourierUser(user)
                || request == null
                || request.customerId == null
                || request.costCenterId == null) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isCustomerUser(user)) {
            if (!request.customerId.equals(user.getCustomerId())
                    || !hasCostCenterAccess(user, request.costCenterId)) {
                return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
            }
        } else if (!accessControlService.canAccessHeadquarters(user, request.hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }

        Long effectiveHqId = user != null && user.getHeadquartersId() != null ? user.getHeadquartersId() : request.hqId;
        List<JobBatchService.BatchJobRow> rows = request.rows != null
                ? request.rows.stream()
                .map(row -> new JobBatchService.BatchJobRow(
                        row.orderTime,
                        row.description,
                        row.commissionNo,
                        row.totalPrice,
                        row.courierPrice,
                        row.courierId
                ))
                .toList()
                : List.of();
        return ResponseEntity.ok(jobBatchService.createBatchJobs(
                effectiveHqId,
                request.customerId,
                request.costCenterId,
                rows
        ));
    }

    @PutMapping("/{id}")
    public ResponseEntity<Job> update(@PathVariable Long id,
                                      @RequestBody Job updates,
                                      Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        sanitizeLonghaulUpdate(user, updates);
        return ResponseEntity.ok(jobService.updateJob(id, updates));
    }

    @PutMapping("/{id}/details")
    public ResponseEntity<Job> updateDetails(@PathVariable Long id,
                                             @RequestBody JobCreateRequest request,
                                             Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().<Job>build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing)
                || !accessControlService.canManageJobs(user)
                || isCourierUser(user)
                || !requestWithinCustomerScope(user, request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).<Job>build();
        }
        if (request.job != null && user != null && user.getHeadquartersId() != null && !isHeadquartersUser(user)) {
            request.job.setHeadquartersId(user.getHeadquartersId());
        }
        sanitizeLonghaulUpdate(user, request.job);
        Job updated = jobService.updateJob(id, request.job);
        for (Tour tour : mapTours(id, request.tours)) {
            jobService.saveTour(tour);
        }
        jobService.replaceTourArticles(id, request.articles != null ? request.articles : List.of());
        return ResponseEntity.ok(updated);
    }

    @PutMapping("/{id}/cancel")
    public ResponseEntity<Void> cancel(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing)
                || !accessControlService.canManageJobs(user)
                || isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.cancelJob(id);
        return ResponseEntity.ok().build();
    }

    @PutMapping("/clear-tournames")
    public ResponseEntity<Void> clearTourNames(@RequestBody List<Long> jobIds, Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!isHeadquartersUser(user) || !accessControlService.canManageJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.clearTourNames(jobIds);
        return ResponseEntity.ok().build();
    }

    // Tour endpoints
    @GetMapping("/{id}/tours")
    public ResponseEntity<List<TourDetailDto>> getTours(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findToursForJob(id));
    }

    @PostMapping("/{id}/tours")
    public ResponseEntity<Tour> addTour(@PathVariable Long id,
                                        @RequestBody Tour tour,
                                        Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        UserSessionDto user = sessionUser(authentication);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        tour.setJobId(id);
        return ResponseEntity.ok(jobService.saveTour(tour));
    }

    @PutMapping("/{id}/tours/{tourId}")
    public ResponseEntity<Tour> updateTour(@PathVariable Long id,
                                           @PathVariable Long tourId,
                                           @RequestBody Tour tour,
                                           Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        UserSessionDto user = sessionUser(authentication);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        tour.setId(tourId);
        tour.setJobId(id);
        return ResponseEntity.ok(jobService.saveTour(tour));
    }

    // Tour Article endpoints
    @GetMapping("/{id}/articles")
    public ResponseEntity<List<TourArticle>> getTourArticles(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.getTourArticles(id));
    }

    @PostMapping("/{id}/articles")
    public ResponseEntity<TourArticle> addTourArticle(@PathVariable Long id,
                                                      @RequestBody TourArticle article,
                                                      Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        UserSessionDto user = sessionUser(authentication);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        article.setJobId(id);
        return ResponseEntity.ok(jobService.saveTourArticle(article));
    }

    @GetMapping("/{id}/weight")
    public ResponseEntity<BigDecimal> getTotalWeight(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.getTotalWeight(id));
    }

    @GetMapping("/{id}/packing-pieces")
    public ResponseEntity<Integer> getTotalPackingPieces(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.getTotalPackingPieces(id));
    }

    @GetMapping("/{id}/acceptance-protocol")
    public ResponseEntity<JobAcceptanceProtocolDto> getAcceptanceProtocol(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobAcceptanceProtocolService.getProtocol(id));
    }

    @GetMapping("/{id}/acceptance-protocol/pdf")
    public ResponseEntity<byte[]> exportAcceptanceProtocolPdf(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return fileResponse(
                "abnahmeprotokoll_" + id + ".pdf",
                MediaType.APPLICATION_PDF_VALUE,
                jobAcceptanceProtocolService.createPdf(id)
        );
    }

    @GetMapping("/{id}/acceptance-protocol/signature")
    public ResponseEntity<byte[]> getAcceptanceProtocolSignature(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        byte[] image = jobAcceptanceProtocolService.loadSignatureImage(id);
        if (image.length == 0) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok()
                .header(HttpHeaders.CACHE_CONTROL, "no-store")
                .contentType(Objects.requireNonNull(MediaType.IMAGE_PNG))
                .body(image);
    }

    // Price endpoints
    @GetMapping("/{id}/prices")
    public ResponseEntity<List<JobPrice>> getJobPrices(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobService.getJobPrices(id));
    }

    @GetMapping("/{id}/tourservices")
    public ResponseEntity<List<JobTourServiceRowDto>> getJobTourServices(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(jobTourServiceReadService.findByJobId(id));
    }

    @PostMapping("/{id}/prices")
    public ResponseEntity<Void> saveJobPrice(@PathVariable Long id,
                                             @RequestBody JobPrice price,
                                             Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user) || isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        price.setJobId(id);
        jobService.saveJobPrice(price);
        return ResponseEntity.ok().build();
    }

    @PutMapping("/{id}/prices")
    public ResponseEntity<Void> replaceJobPrices(@PathVariable Long id,
                                                 @RequestBody List<JobPrice> prices,
                                                 Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user) || isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.replaceJobPrices(id, prices != null ? prices : List.of());
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{id}/prices/{metaSort}")
    public ResponseEntity<Void> deleteJobPrice(@PathVariable Long id,
                                               @PathVariable Integer metaSort,
                                               Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        UserSessionDto user = sessionUser(authentication);
        if (!canAccessJob(authentication, existing) || !accessControlService.canManageJobs(user) || isCourierUser(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.deleteJobPrice(id, metaSort);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/pricing/calculate")
    public ResponseEntity<JobPricingResultDto> calculatePricing(@RequestBody JobPricingRequest request,
                                                                Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        JobCreateRequest scopeRequest = new JobCreateRequest();
        scopeRequest.job = request != null ? request.getJob() : null;
        if (request == null
                || request.getJob() == null
                || !accessControlService.canManageJobs(user)
                || isCourierUser(user)
                || !requestWithinCustomerScope(user, scopeRequest)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isHeadquartersUser(user)
                && request.getJob().getHeadquartersId() != null
                && !accessControlService.canAccessHeadquarters(user, request.getJob().getHeadquartersId())) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (request.getJob().getHeadquartersId() == null && user != null && user.getHeadquartersId() != null) {
            request.getJob().setHeadquartersId(user.getHeadquartersId());
        }
        return ResponseEntity.ok(jobPricingService.calculateDraft(
                request.getJob(),
                request.getPrices(),
                request.getTours(),
                request.isManualCourierTotalOverride()
        ));
    }

    // Disposition
    @GetMapping("/{id}/disposition")
    public ResponseEntity<VehicleDisposition> getDisposition(@PathVariable Long id, Authentication authentication) {
        Job existing = jobService.findById(id).orElse(null);
        if (existing == null) {
            return ResponseEntity.notFound().build();
        }
        if (!canAccessJob(authentication, existing)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return jobService.getVehicleDisposition(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @GetMapping("/invoice-summary")
    public ResponseEntity<InvoiceSummaryDto> getInvoiceSummary(
            @RequestParam Long hqId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) java.time.LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) java.time.LocalDate to,
            Authentication authentication) {
        UserSessionDto user = sessionUser(authentication);
        if (!accessControlService.canViewInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (isCustomerUser(user)) {
            return ResponseEntity.ok(viewDataService.buildInvoiceSummaryForCostCenters(
                    user.getHeadquartersId(),
                    user.getAccessibleCostCenterIds(),
                    from,
                    to
            ));
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.buildInvoiceSummary(hqId, from, to));
    }

    private List<JobListRowDto> loadJobListRowsForExport(UserSessionDto user,
                                                         Long hqId,
                                                         LocalDate from,
                                                         LocalDate to,
                                                         String term,
                                                         Integer status,
                                                         Long costCenterId,
                                                         String courierSid,
                                                         Boolean exported) {
        if (isCustomerUser(user)) {
            if (!accessControlService.hasCostCenterAccess(user, costCenterId)) {
                return null;
            }
            return viewDataService.findJobListRowsForCostCenters(
                    user.getHeadquartersId(),
                    user.getAccessibleCostCenterIds(),
                    from,
                    to,
                    term,
                    status,
                    costCenterId,
                    courierSid,
                    exported
            );
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return null;
        }
        return viewDataService.findJobListRows(
                hqId,
                from,
                to,
                term,
                status,
                costCenterId,
                courierSid,
                exported
        );
    }

    private ResponseEntity<byte[]> fileResponse(String filename, String contentType, byte[] body) {
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
                .header(HttpHeaders.CACHE_CONTROL, "no-store")
                .contentType(Objects.requireNonNull(MediaType.parseMediaType(Objects.requireNonNull(contentType))))
                .body(body);
    }

    private String buildJobListFilename(LocalDate from, LocalDate to) {
        return "jobs_" + from + "_" + to + ".csv";
    }

    private boolean canAccessJob(Authentication authentication, Job job) {
        return accessControlService.canAccessJob(sessionUser(authentication), job);
    }

    private void sanitizeLonghaulUpdate(UserSessionDto user, Job job) {
        if (job == null || job.getLonghaul() == null) {
            return;
        }
        if (!accessControlService.canManageLonghaul(user)) {
            job.setLonghaul(null);
            return;
        }
        if (!Integer.valueOf(0).equals(job.getLonghaul()) && !Integer.valueOf(3).equals(job.getLonghaul())) {
            job.setLonghaul(null);
        }
    }

    private boolean requestWithinCustomerScope(UserSessionDto user, JobCreateRequest request) {
        if (user == null || !isCustomerUser(user)) {
            return true;
        }
        if (request == null || request.job == null) {
            return false;
        }
        if (!hasCostCenterAccess(user, request.job.getCostCenterPayerId())
                || !hasCostCenterAccess(user, request.job.getCostCenterRelatedId())) {
            return false;
        }
        if (request.tours != null) {
            for (TourRequest tour : request.tours) {
                if (!hasCostCenterAccess(user, tour.costCenterId)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean hasCostCenterAccess(UserSessionDto user, Long costCenterId) {
        return accessControlService.hasCostCenterAccess(user, costCenterId);
    }

    private boolean matchesDateRange(LocalDateTime value, LocalDateTime from, LocalDateTime to) {
        return value != null && !value.isBefore(from) && !value.isAfter(to);
    }

    private UserSessionDto sessionUser(Authentication authentication) {
        return accessControlService.sessionUser(authentication);
    }

    private boolean isHeadquartersUser(UserSessionDto user) {
        return accessControlService.isHeadquartersUser(user);
    }

    private boolean isCustomerUser(UserSessionDto user) {
        return accessControlService.isCustomerUser(user);
    }

    private boolean isCourierUser(UserSessionDto user) {
        return accessControlService.isCourierUser(user);
    }

    public static class JobCreateRequest {
        public Job job;
        public List<TourRequest> tours;
        public List<TourArticle> articles;
        public List<JobPrice> prices;
    }

    public static class TourRequest {
        public Long id;
        public Integer sort;
        public String comp;
        public String person;
        public String street;
        public String zipcode;
        public String city;
        public String hsno;
        public String commissionNo;
        public Integer status;
        public String remark;
        public String phone;
        public Long costCenterId;
    }

    public static class BatchCreateRequest {
        public Long hqId;
        public Long customerId;
        public Long costCenterId;
        public List<BatchRowRequest> rows;
    }

    public static class BatchRowRequest {
        public LocalDateTime orderTime;
        public String description;
        public String commissionNo;
        public BigDecimal totalPrice;
        public BigDecimal courierPrice;
        public Long courierId;
    }

    private List<Tour> mapTours(Long jobId, List<TourRequest> requests) {
        if (requests == null) {
            return List.of();
        }

        Map<Integer, Tour> existingBySort = new HashMap<>();
        if (jobId != null) {
            for (Tour existingTour : jobService.getTours(jobId)) {
                existingBySort.put(existingTour.getSort(), existingTour);
            }
        }

        return requests.stream().map(request -> {
            Tour tour = new Tour();
            Tour existing = request.sort != null ? existingBySort.get(request.sort) : null;
            if (request.id != null) {
                tour.setId(request.id);
            } else if (existing != null) {
                tour.setId(existing.getId());
            }
            tour.setJobId(jobId);
            tour.setSort(request.sort);
            tour.setComp(request.comp);
            tour.setPerson(request.person);
            tour.setHsno(request.hsno);
            tour.setCommissionNo(request.commissionNo);
            tour.setStatus(request.status);
            tour.setRemark(request.remark);
            tour.setPhone(request.phone);
            tour.setCostCenterId(request.costCenterId);
            tour.setAddressId(saveAddress(existing != null ? existing.getAddressId() : null, request));
            return tour;
        }).toList();
    }

    private Long saveAddress(Long existingAddressId, TourRequest request) {
        Address address = existingAddressId != null
                ? addressRepository.findById(existingAddressId).orElseGet(Address::new)
                : new Address();
        address.setStreet(request.street);
        address.setZipcode(request.zipcode);
        address.setCity(request.city);
        if (address.getCountry() == null || address.getCountry().isBlank()) {
            address.setCountry("DE");
        }
        return addressRepository.save(address).getId();
    }
}
