package com.pressing.modules.dashboard;

import com.pressing.common.dto.PageResponse;
import com.pressing.domain.audit.AuditLog;
import com.pressing.domain.audit.AuditLogRepository;
import com.pressing.domain.invoice.Invoice;
import com.pressing.domain.invoice.InvoiceLine;
import com.pressing.domain.invoice.InvoiceRepository;
import com.pressing.domain.invoice.InvoiceStatus;
import com.pressing.domain.payment.Payment;
import com.pressing.domain.payment.PaymentRepository;
import com.pressing.domain.pickup.Pickup;
import com.pressing.domain.pickup.PickupLine;
import com.pressing.domain.pickup.PickupRepository;
import com.pressing.modules.common.DomainSupport;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

    private final InvoiceRepository invoiceRepository;
    private final PickupRepository pickupRepository;
    private final PaymentRepository paymentRepository;
    private final AuditLogRepository auditLogRepository;
    private final DomainSupport domainSupport;

    public DashboardService(
            InvoiceRepository invoiceRepository,
            PickupRepository pickupRepository,
            PaymentRepository paymentRepository,
            AuditLogRepository auditLogRepository,
            DomainSupport domainSupport
    ) {
        this.invoiceRepository = invoiceRepository;
        this.pickupRepository = pickupRepository;
        this.paymentRepository = paymentRepository;
        this.auditLogRepository = auditLogRepository;
        this.domainSupport = domainSupport;
    }

    // --- RECORDS DTOs DÉTAILLÉS ---

    public record KpiResponse(
            long depositsToday,
            long pickupsToday,
            long pendingPickups,
            long lateTreatments,
            BigDecimal dailyRevenue
    ) {}

    public record DepositDetailDto(
            Long invoiceId,
            String invoiceNumber,
            LocalDateTime depositDateTime,
            String customerName,
            String customerPhone,
            int articleCount,
            String articleSummary, // ex: "2x Costumes, 1x Chemise"
            BigDecimal totalAmount,
            BigDecimal amountDue,
            String operatorName
    ) {}

    public record PickupDetailDto(
            Long pickupId,
            String invoiceNumber,
            LocalDateTime pickupDateTime,
            String customerName,
            String customerPhone,
            int quantityPickedUp,
            String articleSummary,
            String operatorName
    ) {}

    public record PendingPickupDto(
            Long invoiceId,
            String invoiceNumber,
            LocalDateTime depositDateTime,
            LocalDate expectedPickupDate,
            String customerName,
            String customerPhone,
            int totalArticles,
            int remainingArticles,
            BigDecimal amountDue,
            String status
    ) {}

    public record LateTreatmentDto(
            Long invoiceId,
            String invoiceNumber,
            LocalDateTime depositDateTime,
            LocalDate expectedPickupDate,
            long daysLate,
            String customerName,
            String customerPhone,
            int articleCount,
            String status
    ) {}

    public record RevenueDetailDto(
            Long paymentId,
            // "LAVAGE" ou "REPASSAGE" — recouvrement unifié, un règlement peut porter sur les deux types de documents
            String documentType,
            String invoiceNumber,
            LocalDateTime paymentDateTime,
            String customerName,
            BigDecimal amount,
            String paymentMethod,
            String operatorName
    ) {}

    public record AgendaRow(
            String invoiceNumber,
            LocalDateTime depositDateTime,
            LocalDate expectedPickupDate,
            String customerName,
            String customerPhone,
            int articleCount,
            BigDecimal amountDue,
            String status
    ) {}

    public record AuditRow(
            LocalDateTime eventDateTime,
            String details,
            String operatorName,
            String operatorId,
            String action
    ) {}

    // --- LOGIQUE DES KPIS COMPTEURS ---

    public KpiResponse kpis() {
        Long siteId = domainSupport.siteId();
        LocalDateTime start = LocalDate.now().atStartOfDay();
        LocalDateTime end = start.plusDays(1);

        long deposits = invoiceRepository.countBySiteIdAndDepositDateTimeBetweenAndStatusNot(siteId, start, end, InvoiceStatus.CANCELLED);
        List<Pickup> pickups = pickupRepository.findBySiteIdAndPickupDateTimeBetween(siteId, start, end);
        long pickupCount = pickups.size();

        Specification<Invoice> pendingSpec = (root, query, cb) -> cb.and(
                cb.equal(root.get("siteId"), siteId),
                root.get("status").in(InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.PARTIALLY_PICKED_UP)
        );
        long pendingPickups = invoiceRepository.count(pendingSpec);

        Specification<Invoice> lateSpec = (root, query, cb) -> cb.and(
                cb.equal(root.get("siteId"), siteId),
                cb.lessThan(root.get("expectedPickupDate"), LocalDate.now()),
                root.get("status").in(InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.DRAFT, InvoiceStatus.PARTIALLY_PICKED_UP)
        );
        long lates = invoiceRepository.count(lateSpec);

        BigDecimal revenue = paymentRepository.findBySiteIdAndPaymentDateTimeBetween(siteId, start, end).stream()
                .map(Payment::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        return new KpiResponse(deposits, pickupCount, pendingPickups, lates, revenue);
    }

    // --- 1. CLIC DÉPÔTS (DÉTAILS) ---
    public PageResponse<DepositDetailDto> getDepositsDetails(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        LocalDate startDate = from != null ? from : LocalDate.now();
        LocalDate endDate = to != null ? to : LocalDate.now();

        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.notEqual(root.get("status"), InvoiceStatus.CANCELLED));
            predicates.add(cb.greaterThanOrEqualTo(root.get("depositDateTime"), startDate.atStartOfDay()));
            predicates.add(cb.lessThan(root.get("depositDateTime"), endDate.plusDays(1).atStartOfDay()));

            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var customer = root.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(customer.get("phone"), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };

        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("depositDateTime").descending()))
                .map(inv -> {
                    String articlesSummary = inv.getLines().stream()
                            .map(l -> l.getQuantity() + "x " + l.getDesignation())
                            .collect(Collectors.joining(", "));
                    return new DepositDetailDto(
                            inv.getId(),
                            inv.getInvoiceNumber(),
                            inv.getDepositDateTime(),
                            inv.getCustomer().getFirstName() + " " + inv.getCustomer().getLastName(),
                            inv.getCustomer().getPhone(),
                            inv.getArticleCount(),
                            articlesSummary,
                            inv.getAmountTtc(),
                            inv.getAmountDue(),
                            inv.getCreatedBy()
                    );
                }));
    }

    // --- 2. CLIC ARTICLES RETIRÉS (DÉTAILS) ---
    public PageResponse<PickupDetailDto> getPickupsDetails(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        LocalDate startDate = from != null ? from : LocalDate.now();
        LocalDate endDate = to != null ? to : LocalDate.now();

        Specification<Pickup> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.greaterThanOrEqualTo(root.get("pickupDateTime"), startDate.atStartOfDay()));
            predicates.add(cb.lessThan(root.get("pickupDateTime"), endDate.plusDays(1).atStartOfDay()));

            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var invoice = root.join("invoice", JoinType.LEFT);
                var customer = invoice.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(invoice.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(customer.get("phone"), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };

        return PageResponse.from(pickupRepository.findAll(spec, PageRequest.of(page, size, Sort.by("pickupDateTime").descending()))
                .map(p -> {
                    int qty = p.getLines().stream().mapToInt(PickupLine::getQuantity).sum();
                    String summary = p.getLines().stream()
                            .map(l -> l.getQuantity() + "x " + l.getInvoiceLine().getDesignation())
                            .collect(Collectors.joining(", "));
                    Invoice inv = p.getInvoice();
                    return new PickupDetailDto(
                            p.getId(),
                            inv.getInvoiceNumber(),
                            p.getPickupDateTime(),
                            inv.getCustomer().getFirstName() + " " + inv.getCustomer().getLastName(),
                            inv.getCustomer().getPhone(),
                            qty,
                            summary,
                            p.getOperatorName()
                    );
                }));
    }

    // --- 3. CLIC RETRAITS EN ATTENTE (DÉTAILS) ---
    public PageResponse<PendingPickupDto> getPendingPickupsDetails(String q, int page, int size) {
        Long siteId = domainSupport.siteId();

        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(root.get("status").in(
                    InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.PARTIALLY_PICKED_UP
            ));

            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var customer = root.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(customer.get("phone"), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };

        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("expectedPickupDate").ascending()))
                .map(inv -> {
                    int remaining = inv.getLines().stream()
                            .mapToInt(l -> l.getQuantity() - l.getQuantityPickedUp())
                            .sum();
                    return new PendingPickupDto(
                            inv.getId(),
                            inv.getInvoiceNumber(),
                            inv.getDepositDateTime(),
                            inv.getExpectedPickupDate(),
                            inv.getCustomer().getFirstName() + " " + inv.getCustomer().getLastName(),
                            inv.getCustomer().getPhone(),
                            inv.getArticleCount(),
                            remaining,
                            inv.getAmountDue(),
                            inv.getStatus().name()
                    );
                }));
    }

    // --- 4. CLIC RETARDS DE TRAITEMENT (DÉTAILS) ---
    public PageResponse<LateTreatmentDto> getLateTreatmentsDetails(String q, int page, int size) {
        Long siteId = domainSupport.siteId();
        LocalDate today = LocalDate.now();

        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.lessThan(root.get("expectedPickupDate"), today));
            predicates.add(root.get("status").in(
                    InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.DRAFT, InvoiceStatus.PARTIALLY_PICKED_UP
            ));

            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var customer = root.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(customer.get("phone"), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };

        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("expectedPickupDate").ascending()))
                .map(inv -> {
                    long daysLate = java.time.temporal.ChronoUnit.DAYS.between(inv.getExpectedPickupDate(), today);
                    return new LateTreatmentDto(
                            inv.getId(),
                            inv.getInvoiceNumber(),
                            inv.getDepositDateTime(),
                            inv.getExpectedPickupDate(),
                            daysLate,
                            inv.getCustomer().getFirstName() + " " + inv.getCustomer().getLastName(),
                            inv.getCustomer().getPhone(),
                            inv.getArticleCount(),
                            inv.getStatus().name()
                    );
                }));
    }

    // --- 5. CLIC RECETTE DU JOUR (DÉTAILS PAIEMENTS) ---
    public PageResponse<RevenueDetailDto> getRevenueDetails(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        LocalDate startDate = from != null ? from : LocalDate.now();
        LocalDate endDate = to != null ? to : LocalDate.now();

        Specification<Payment> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.greaterThanOrEqualTo(root.get("paymentDateTime"), startDate.atStartOfDay()));
            predicates.add(cb.lessThan(root.get("paymentDateTime"), endDate.plusDays(1).atStartOfDay()));

            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var invoice = root.join("invoice", JoinType.LEFT);
                var ironingInvoice = root.join("ironingInvoice", JoinType.LEFT);
                var customer = root.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(invoice.get("invoiceNumber")), like),
                        cb.like(cb.lower(ironingInvoice.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(cb.lower(customer.get("firstName")), like),
                        cb.like(customer.get("phone"), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };

        return PageResponse.from(paymentRepository.findAll(spec, PageRequest.of(page, size, Sort.by("paymentDateTime").descending()))
                .map(this::toRevenueDetail));
    }

    // Recouvrement unifié : un règlement peut porter sur une facture de lavage (Invoice)
    // ou un reçu de repassage (IroningInvoice) — jamais les deux.
    private RevenueDetailDto toRevenueDetail(Payment p) {
        boolean isIroning = p.getIroningInvoice() != null;
        String documentType = isIroning ? "REPASSAGE" : "LAVAGE";
        String documentNumber = isIroning ? p.getIroningInvoice().getInvoiceNumber() : p.getInvoice().getInvoiceNumber();
        String customerName = p.getCustomer() != null
                ? p.getCustomer().getFirstName() + " " + p.getCustomer().getLastName()
                : "—";
        return new RevenueDetailDto(
                p.getId(),
                documentType,
                documentNumber,
                p.getPaymentDateTime(),
                customerName,
                p.getAmount(),
                p.getMethod(),
                p.getOperatorName()
        );
    }

    // --- AGENDA DES RENDEZ-VOUS ---
    public PageResponse<AgendaRow> agenda(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.notEqual(root.get("status"), InvoiceStatus.CANCELLED));
            if (from != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("depositDateTime"), from.atStartOfDay()));
            }
            if (to != null) {
                predicates.add(cb.lessThan(root.get("depositDateTime"), to.plusDays(1).atStartOfDay()));
            }
            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var customer = root.join("customer");
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(cb.lower(customer.get("phone")), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };
        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("depositDateTime").descending()))
                .map(this::toAgenda));
    }

    // --- JOURNAL DES OPÉRATIONS (AUDIT LOGS) ---
    public PageResponse<AuditRow> audits(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        Specification<AuditLog> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            if (from != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("eventDateTime"), from.atStartOfDay()));
            }
            if (to != null) {
                predicates.add(cb.lessThan(root.get("eventDateTime"), to.plusDays(1).atStartOfDay()));
            }
            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("details")), like),
                        cb.like(cb.lower(root.get("operatorName")), like),
                        cb.like(cb.lower(root.get("action")), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };
        return PageResponse.from(auditLogRepository.findAll(spec, PageRequest.of(page, size, Sort.by("eventDateTime").descending()))
                .map(a -> new AuditRow(a.getEventDateTime(), a.getDetails(), a.getOperatorName(), a.getOperatorId(), a.getAction())));
    }

    private AgendaRow toAgenda(Invoice invoice) {
        return new AgendaRow(
                invoice.getInvoiceNumber(),
                invoice.getDepositDateTime(),
                invoice.getExpectedPickupDate(),
                invoice.getCustomer().getFirstName() + " " + invoice.getCustomer().getLastName(),
                invoice.getCustomer().getPhone(),
                invoice.getArticleCount(),
                invoice.getAmountDue(),
                invoice.getStatus().name()
        );
    }
}

/*package com.pressing.modules.dashboard;

import com.pressing.common.dto.PageResponse;
import com.pressing.domain.audit.AuditLog;
import com.pressing.domain.audit.AuditLogRepository;
import com.pressing.domain.invoice.Invoice;
import com.pressing.domain.invoice.InvoiceRepository;
import com.pressing.domain.invoice.InvoiceStatus;
import com.pressing.domain.invoice.PaymentStatus;
import com.pressing.domain.payment.Payment;
import com.pressing.domain.payment.PaymentRepository;
import com.pressing.domain.pickup.Pickup;
import com.pressing.domain.pickup.PickupRepository;
import com.pressing.modules.common.DomainSupport;
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

    private final InvoiceRepository invoiceRepository;
    private final PickupRepository pickupRepository;
    private final PaymentRepository paymentRepository;
    private final AuditLogRepository auditLogRepository;
    private final DomainSupport domainSupport;

    public DashboardService(
            InvoiceRepository invoiceRepository,
            PickupRepository pickupRepository,
            PaymentRepository paymentRepository,
            AuditLogRepository auditLogRepository,
            DomainSupport domainSupport
    ) {
        this.invoiceRepository = invoiceRepository;
        this.pickupRepository = pickupRepository;
        this.paymentRepository = paymentRepository;
        this.auditLogRepository = auditLogRepository;
        this.domainSupport = domainSupport;
    }

    public record KpiResponse(
            long depositsToday,
            long pickupsToday,
            long pendingPickups,
            long lateTreatments,
            BigDecimal dailyRevenue
    ) {
    }

    public record AgendaRow(
            String invoiceNumber,
            LocalDateTime depositDateTime,
            LocalDate expectedPickupDate,
            String customerName,
            String customerPhone,
            int articleCount,
            BigDecimal amountDue,
            String status
    ) {
    }

    public record AuditRow(
            LocalDateTime eventDateTime,
            String details,
            String operatorName,
            String operatorId,
            String action
    ) {
    }

    public KpiResponse kpis() {
        Long siteId = domainSupport.siteId();
        LocalDateTime start = LocalDate.now().atStartOfDay();
        LocalDateTime end = start.plusDays(1);
        long deposits = invoiceRepository.countBySiteIdAndDepositDateTimeBetweenAndStatusNot(siteId, start, end, InvoiceStatus.CANCELLED);
        List<Pickup> pickups = pickupRepository.findBySiteIdAndPickupDateTimeBetween(siteId, start, end);
        long pickupCount = pickups.size();
        Specification<Invoice> pending = (root, query, cb) -> cb.and(
                cb.equal(root.get("siteId"), siteId),
                root.get("status").in(InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.PARTIALLY_PICKED_UP)
        );
        long pendingPickups = invoiceRepository.count(pending);
        Specification<Invoice> late = (root, query, cb) -> cb.and(
                cb.equal(root.get("siteId"), siteId),
                cb.lessThan(root.get("expectedPickupDate"), LocalDate.now()),
                root.get("status").in(InvoiceStatus.VALIDATED, InvoiceStatus.IN_PROGRESS, InvoiceStatus.READY, InvoiceStatus.DRAFT, InvoiceStatus.PARTIALLY_PICKED_UP)
        );
        long lates = invoiceRepository.count(late);
        BigDecimal revenue = paymentRepository.findBySiteIdAndPaymentDateTimeBetween(siteId, start, end).stream()
                .map(Payment::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return new KpiResponse(deposits, pickupCount, pendingPickups, lates, revenue);
    }

    public PageResponse<AgendaRow> agenda(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            predicates.add(cb.notEqual(root.get("status"), InvoiceStatus.CANCELLED));
            if (from != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("depositDateTime"), from.atStartOfDay()));
            }
            if (to != null) {
                predicates.add(cb.lessThan(root.get("depositDateTime"), to.plusDays(1).atStartOfDay()));
            }
            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                var customer = root.join("customer");
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(cb.lower(customer.get("phone")), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };
        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("depositDateTime").descending()))
                .map(this::toAgenda));
    }

    public PageResponse<AuditRow> audits(String q, LocalDate from, LocalDate to, int page, int size) {
        Long siteId = domainSupport.siteId();
        Specification<AuditLog> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            if (from != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("eventDateTime"), from.atStartOfDay()));
            }
            if (to != null) {
                predicates.add(cb.lessThan(root.get("eventDateTime"), to.plusDays(1).atStartOfDay()));
            }
            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("details")), like),
                        cb.like(cb.lower(root.get("operatorName")), like),
                        cb.like(cb.lower(root.get("action")), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };
        return PageResponse.from(auditLogRepository.findAll(spec, PageRequest.of(page, size, Sort.by("eventDateTime").descending()))
                .map(a -> new AuditRow(a.getEventDateTime(), a.getDetails(), a.getOperatorName(), a.getOperatorId(), a.getAction())));
    }

    private AgendaRow toAgenda(Invoice invoice) {
        return new AgendaRow(
                invoice.getInvoiceNumber(),
                invoice.getDepositDateTime(),
                invoice.getExpectedPickupDate(),
                invoice.getCustomer().getFirstName() + " " + invoice.getCustomer().getLastName(),
                invoice.getCustomer().getPhone(),
                invoice.getArticleCount(),
                invoice.getAmountDue(),
                invoice.getStatus().name()
        );
    }
}
*/