package de.assecutor.votianlt.pages.view;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.NumberField;

import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import de.assecutor.votianlt.model.Customer;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobServiceSelection;
import de.assecutor.votianlt.model.Service;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.model.InvoiceTemplate;
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.invoices.CustomerInvoiceItem;
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
import de.assecutor.votianlt.pages.service.CustomerService;
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.ServiceRepository;
import de.assecutor.votianlt.repository.UserRepository;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.CustomerInvoiceService;
import de.assecutor.votianlt.service.InvoiceLifecycleException;
import de.assecutor.votianlt.service.InvoiceLifecycleService;
import de.assecutor.votianlt.service.InvoiceTemplateService;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.IFrame;

@Route(value = "create_invoice", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed({ "USER" })
@Slf4j
public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter<String>, HasDynamicTitle {

    private final JobRepository jobRepository;
    private final ServiceRepository serviceRepository;
    private final UserRepository userRepository;
    private final CustomerInvoiceService customerInvoiceService;
    private final InvoiceTemplateService invoiceTemplateService;
    private final SecurityService securityService;
    private final UserInvoiceDataService userInvoiceDataService;
    private final CustomerService customerService;
    private final InvoiceLifecycleService invoiceLifecycleService;
    private User currentUser;
    private Job currentJob;
    private List<ServiceRow> gridRows = new ArrayList<>();
    private Grid<ServiceRow> servicesGrid;
    private Div servicesSection;
    private Div summarySection;
    private NumberField vatField;

    /**
     * Helper class to represent a row in the services grid
     */
    public static class ServiceRow {
        private Service service;
        private JobServiceSelection selection;

        public ServiceRow() {
            this.service = null;
        }

        public ServiceRow(Service service) {
            this.service = service;
        }

        public ServiceRow(Service service, JobServiceSelection selection) {
            this.service = service;
            this.selection = selection;
        }

        public Service getService() {
            return service;
        }

        public void setService(Service service) {
            this.service = service;
        }

        public JobServiceSelection getSelection() {
            return selection;
        }

        public void setSelection(JobServiceSelection selection) {
            this.selection = selection;
        }

        public boolean isEmpty() {
            return service == null;
        }
    }

    @Autowired
    public CreateInvoiceView(JobRepository jobRepository, ServiceRepository serviceRepository,
            UserRepository userRepository, CustomerInvoiceService customerInvoiceService,
            InvoiceTemplateService invoiceTemplateService, SecurityService securityService,
            UserInvoiceDataService userInvoiceDataService, CustomerService customerService,
            InvoiceLifecycleService invoiceLifecycleService) {
        this.jobRepository = jobRepository;
        this.serviceRepository = serviceRepository;
        this.userRepository = userRepository;
        this.customerInvoiceService = customerInvoiceService;
        this.invoiceTemplateService = invoiceTemplateService;
        this.securityService = securityService;
        this.userInvoiceDataService = userInvoiceDataService;
        this.customerService = customerService;
        this.invoiceLifecycleService = invoiceLifecycleService;
        setSizeFull();
        setPadding(true);
        setSpacing(true);
        addClassName("data-view");
    }

    /**
     * Lädt die Services, die beim Job-Erstellen ausgewählt wurden.
     */
    private void loadSelectedServicesFromJob() {
        if (currentJob.getSelectedServices() != null && !currentJob.getSelectedServices().isEmpty()) {
            gridRows.clear();
            for (JobServiceSelection selection : currentJob.getSelectedServices()) {
                if (selection.getServiceId() == null) {
                    continue;
                }
                serviceRepository.findById(selection.getServiceId()).ifPresent(service -> {
                    gridRows.add(new ServiceRow(service, selection));
                });
            }
            return;
        }

        if (currentJob.getServiceIds() != null && !currentJob.getServiceIds().isEmpty()) {
            gridRows.clear();
            for (String serviceId : currentJob.getServiceIds()) {
                serviceRepository.findById(serviceId).ifPresent(service -> {
                    gridRows.add(new ServiceRow(service));
                });
            }
        }
    }

    @Override
    public void setParameter(BeforeEvent event, String jobIdHex) {
        try {
            ObjectId jobId = new ObjectId(jobIdHex);
            loadJob(jobId);
        } catch (Exception e) {
            log.error("Fehler beim Parsen der Job-ID: " + jobIdHex, e);
            add(new Span(getTranslation("createinvoice.error.invalidid")));
        }
    }

    public void loadJob(ObjectId jobId) {
        currentJob = jobRepository.findById(jobId).orElse(null);
        if (currentJob == null) {
            add(new Span(getTranslation("createinvoice.error.notfound")));
            return;
        }

        currentUser = securityService.getAuthenticatedUser()
                .flatMap(auth -> userRepository.findByEmail(auth.getUsername())).orElse(null);

        createInvoiceView();
    }

    private void createInvoiceView() {
        removeAll();

        // Title
        H2 title = new H2(getTranslation("createinvoice.title", currentJob.getJobNumber()));
        add(title);

        // Load previously selected services from job
        loadSelectedServicesFromJob();

        // Job Details Section
        Div jobDetailsSection = createJobDetailsSection();
        add(jobDetailsSection);

        // Route Information Section (if available)
        if (currentJob.getRouteDistanceKm() != null && currentJob.getRouteDistanceKm() > 0) {
            Div routeInfoSection = createRouteInfoSection();
            add(routeInfoSection);
        }

        // Services Selection Section
        Div servicesSection = createServicesSelectionSection();
        add(servicesSection);

        // VAT Section (must exist before summary so effectiveVatRate() can read the field)
        Div vatSection = createVatSection();
        add(vatSection);

        // Summary Section
        summarySection = createSummarySection();
        add(summarySection);

        // Create Invoice Button
        Button createInvoiceButton = new Button(getTranslation("createinvoice.button.create"));
        createInvoiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        createInvoiceButton.addClickListener(e -> createInvoice());
        createInvoiceButton.getStyle().set("margin-bottom", "15px");
        add(createInvoiceButton);
    }

    private Div createJobDetailsSection() {
        Div section = new Div();
        section.setWidthFull();
        section.addClassName("invoice-summary-card");
        section.getStyle().set("margin-bottom", "var(--lumo-space-m)");

        H3 sectionTitle = new H3(getTranslation("createinvoice.section.job"));
        section.add(sectionTitle);

        // Job information
        VerticalLayout jobInfo = new VerticalLayout();
        jobInfo.setSpacing(true);
        jobInfo.setWidthFull();

        jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.jobnumber")),
                new Span(currentJob.getJobNumber())));
        jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.customer")),
                new Span(extractCompanyName(currentJob.getCustomerSelection()))));
        jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.status")),
                new Span(currentJob.getStatus().toString())));
        jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.price")),
                new Span(currentJob.getPrice() + " €")));

        section.add(jobInfo);
        return section;
    }

    private Div createRouteInfoSection() {
        Div section = new Div();
        section.setWidthFull();
        section.addClassNames("detail-card", "detail-card--accent");
        section.getStyle().set("margin-bottom", "var(--lumo-space-m)");

        H3 sectionTitle = new H3(getTranslation("createinvoice.section.route"));
        section.add(sectionTitle);

        VerticalLayout routeInfo = new VerticalLayout();
        routeInfo.setSpacing(true);
        routeInfo.setWidthFull();

        Double distance = currentJob.getRouteDistanceKm();
        if (distance != null) {
            routeInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.route.distance")),
                    new Span(String.format("%.1f km", distance))));
        }

        Integer durationSeconds = currentJob.getRouteDurationSeconds();
        if (durationSeconds != null && durationSeconds > 0) {
            String formattedDuration = formatDuration(durationSeconds);
            routeInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.route.duration")),
                    new Span(formattedDuration)));
        }

        section.add(routeInfo);
        return section;
    }

    private Div createServicesSelectionSection() {
        servicesSection = new Div();
        servicesSection.setWidthFull();
        servicesSection.addClassName("services-panel");
        servicesSection.getStyle().set("margin-bottom", "var(--lumo-space-m)");

        H3 sectionTitle = new H3(getTranslation("createinvoice.section.services"));
        servicesSection.add(sectionTitle);

        // Create grid with read-only rows
        servicesGrid = new Grid<>();
        servicesGrid.setWidthFull();
        servicesGrid.setAllRowsVisible(true);
        servicesGrid.addClassName("data-grid");

        // Service name column (read-only)
        servicesGrid.addColumn(row -> {
            if (row.getService() != null) {
                return row.getService().getName();
            }
            return "";
        }).setHeader(getTranslation("createinvoice.column.service")).setAutoWidth(true).setFlexGrow(2);

        servicesGrid.addColumn(this::getDeliveryStationLabel)
                .setHeader(getTranslation("addjob.services.deliverystation")).setAutoWidth(true).setFlexGrow(1);

        // Calculation basis column (read-only)
        servicesGrid.addColumn(row -> {
            if (row.getService() != null && row.getService().getCalculationBasis() != null) {
                return switch (row.getService().getCalculationBasis()) {
                case DISTANCE -> getTranslation("addjob.services.basis.distance");
                case TIME -> getTranslation("addjob.services.basis.time");
                case FLAT_RATE -> getTranslation("addjob.services.basis.flatrate");
                };
            }
            return "";
        }).setHeader(getTranslation("createinvoice.column.basis")).setAutoWidth(true).setFlexGrow(1);

        // Price column (read-only)
        servicesGrid.addColumn(row -> {
            if (row.getService() != null) {
                BigDecimal price = calculateServicePrice(row);
                if (price != null) {
                    return price.setScale(2, RoundingMode.HALF_UP) + " €";
                }
            }
            return "";
        }).setHeader(getTranslation("common.price")).setAutoWidth(true).setFlexGrow(1).setKey("price");

        servicesGrid.setItems(gridRows);
        servicesSection.add(servicesGrid);

        return servicesSection;
    }

    private List<ServiceRow> getSelectedServices() {
        return gridRows.stream().filter(row -> row.getService() != null).toList();
    }

    private Div createSummarySection() {
        Div section = new Div();
        section.setWidthFull();
        section.addClassName("invoice-section-card");
        section.getStyle().set("margin-bottom", "var(--lumo-space-m)");
        populateSummarySection(section);
        return section;
    }

    private void populateSummarySection(Div section) {
        H3 sectionTitle = new H3(getTranslation("createinvoice.section.summary"));
        section.add(sectionTitle);

        BigDecimal netAmount = calculateNetAmount();
        BigDecimal vatRate = effectiveVatRate();
        BigDecimal vatAmount = netAmount.multiply(vatRate);
        BigDecimal totalAmount = netAmount.add(vatAmount);

        Div priceTable = new Div();
        priceTable.addClassName("price-table");

        priceTable.add(createPriceRow(getTranslation("createinvoice.summary.net") + ":",
                netAmount.setScale(2, RoundingMode.HALF_UP) + " €", false));
        priceTable.add(createPriceRow(getTranslation("createinvoice.summary.total") + ":",
                totalAmount.setScale(2, RoundingMode.HALF_UP) + " €", true));

        section.add(priceTable);
    }

    private Div createVatSection() {
        Div section = new Div();
        section.setWidthFull();
        section.addClassName("invoice-section-card");
        section.getStyle().set("margin-bottom", "var(--lumo-space-m)");

        H3 sectionTitle = new H3(getTranslation("createinvoice.section.vat"));
        section.add(sectionTitle);

        vatField = new NumberField();
        vatField.setLabel(getTranslation("createinvoice.field.vatrate"));
        vatField.setSuffixComponent(new Span("%"));
        vatField.setStep(0.01);
        vatField.setMin(0);
        BigDecimal initialRate = currentUser != null && currentUser.getVatRate() != null
                ? currentUser.getVatRate()
                : Service.FIXED_VAT_RATE;
        vatField.setValue(initialRate.multiply(new BigDecimal("100")).doubleValue());
        vatField.addValueChangeListener(e -> refreshSummarySection());

        section.add(vatField);
        return section;
    }

    private void refreshSummarySection() {
        if (summarySection == null) {
            return;
        }
        summarySection.removeAll();
        populateSummarySection(summarySection);
    }

    private Div createPriceRow(String label, String value, boolean bold) {
        Div row = new Div();
        row.addClassName("price-row");
        if (bold) {
            row.addClassName("price-row--strong");
        }

        Span labelSpan = new Span(label);
        labelSpan.addClassName("price-row-label");

        Span valueSpan = new Span(value);
        valueSpan.addClassName("price-row-value");

        row.add(labelSpan, valueSpan);
        return row;
    }

    private String getDeliveryStationLabel(ServiceRow row) {
        JobServiceSelection selection = row.getSelection();
        if (selection == null || selection.getDeliveryStationOrder() == null) {
            return "-";
        }
        return "Lieferstation " + (selection.getDeliveryStationOrder() + 1);
    }

    private BigDecimal calculateServicePrice(ServiceRow row) {
        Service service = row.getService();
        JobServiceSelection selection = row.getSelection();
        if (service.getCalculationBasis() == null) {
            return null;
        }

        if (service.getCalculationBasis() == Service.CalculationBasis.FLAT_RATE && service.getPrice() != null) {
            return service.getPrice();
        } else if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE
                && service.getPricePerKilometer() != null && getRouteDistanceKm(selection) != null) {
            BigDecimal kilometers = BigDecimal.valueOf(getRouteDistanceKm(selection));
            return service.getPricePerKilometer().multiply(kilometers);
        } else if (service.getCalculationBasis() == Service.CalculationBasis.TIME
                && service.getPricePer15Minutes() != null && getTimeIn15MinUnits(selection) != null) {
            BigDecimal timeUnits = new BigDecimal(getTimeIn15MinUnits(selection));
            return service.getPricePer15Minutes().multiply(timeUnits);
        }

        return null;
    }

    private Double getRouteDistanceKm(JobServiceSelection selection) {
        if (selection != null && selection.getRouteDistanceKm() != null) {
            return selection.getRouteDistanceKm();
        }
        return currentJob.getRouteDistanceKm();
    }

    private Integer getTimeIn15MinUnits(JobServiceSelection selection) {
        Integer durationSeconds = selection != null && selection.getRouteDurationSeconds() != null
                ? selection.getRouteDurationSeconds()
                : currentJob.getRouteDurationSeconds();
        if (durationSeconds == null || durationSeconds <= 0) {
            return currentJob.getTimeIn15MinUnits();
        }

        int units = durationSeconds / 900;
        if (durationSeconds % 900 > 0) {
            units++;
        }
        return units;
    }

    private BigDecimal effectiveVatRate() {
        if (vatField != null && vatField.getValue() != null) {
            return new BigDecimal(Double.toString(vatField.getValue()))
                    .divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
        }
        if (currentUser != null && currentUser.getVatRate() != null) {
            return currentUser.getVatRate();
        }
        return Service.FIXED_VAT_RATE;
    }

    private BigDecimal calculateNetAmount() {
        BigDecimal total = BigDecimal.ZERO;

        for (ServiceRow row : getSelectedServices()) {
            Service service = row.getService();
            if (service.getCalculationBasis() == Service.CalculationBasis.FLAT_RATE && service.getPrice() != null) {
                total = total.add(service.getPrice());
            } else if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE
                    && service.getPricePerKilometer() != null && getRouteDistanceKm(row.getSelection()) != null) {
                BigDecimal kilometers = BigDecimal.valueOf(getRouteDistanceKm(row.getSelection()));
                BigDecimal serviceTotal = service.getPricePerKilometer().multiply(kilometers);
                total = total.add(serviceTotal);
            } else if (service.getCalculationBasis() == Service.CalculationBasis.TIME
                    && service.getPricePer15Minutes() != null && getTimeIn15MinUnits(row.getSelection()) != null) {
                BigDecimal timeUnits = new BigDecimal(getTimeIn15MinUnits(row.getSelection()));
                BigDecimal serviceTotal = service.getPricePer15Minutes().multiply(timeUnits);
                total = total.add(serviceTotal);
            }
        }

        return total;
    }

    private String extractCompanyName(String customerSelection) {
        if (customerSelection == null || customerSelection.isBlank()) {
            return "";
        }
        // Format: "Firmenname | Vorname Nachname" - extrahiere nur den Firmennamen
        int separatorIndex = customerSelection.indexOf(" | ");
        if (separatorIndex > 0) {
            return customerSelection.substring(0, separatorIndex).trim();
        }
        return customerSelection.trim();
    }

    private void createInvoice() {
        if (getSelectedServices().isEmpty()) {
            Notification.show(getTranslation("createinvoice.notification.noservices"), 3000,
                    Notification.Position.BOTTOM_END);
            return;
        }

        try {
            Optional<User> currentUserOpt = securityService.getAuthenticatedUser()
                    .flatMap(auth -> userRepository.findByEmail(auth.getUsername()));

            if (currentUserOpt.isEmpty()) {
                Notification.show(getTranslation("createinvoice.notification.nouser"), 3000,
                        Notification.Position.BOTTOM_END);
                return;
            }

            currentUser = currentUserOpt.get();

            Optional<InvoiceTemplate> templateOpt = invoiceTemplateService
                    .getTemplateByUserId(currentUser.getId().toString());
            if (templateOpt.isEmpty()) {
                Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
                        Notification.Position.BOTTOM_END);
                return;
            }

            String templateData = templateOpt.get().getTemplateData();
            if (templateData == null || templateData.isBlank()) {
                Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
                        Notification.Position.BOTTOM_END);
                return;
            }

            // Vorschau-PDF mit Platzhalter-Nummer generieren (noch kein Speichern)
            byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser,
                    getTranslation("createinvoice.preview.number"));

            showPdfPreviewDialog(pdfBytes, templateData, currentUser);

        } catch (Exception ex) {
            log.error("Fehler beim Erstellen der Rechnungsvorschau", ex);
            Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
                    Notification.Position.BOTTOM_END);
        }
    }

    private void saveInvoice(String templateData, User user, Dialog previewDialog) {
        try {
            String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(user.getId());

            BigDecimal netAmount = calculateNetAmount();
            BigDecimal vatRate = effectiveVatRate();
            BigDecimal vatAmount = netAmount.multiply(vatRate);
            BigDecimal totalAmount = netAmount.add(vatAmount);

            byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, user, invoiceNumber);

            CustomerInvoice invoice = new CustomerInvoice();
            invoice.setInvoiceNumber(invoiceNumber);
            invoice.setInvoiceDate(java.time.LocalDate.now());
            invoice.setJobId(currentJob.getId().toHexString());
            invoice.setUserId(user.getId().toHexString());
            invoice.setNetAmount(netAmount);
            invoice.setVatRate(vatRate);
            invoice.setVatAmount(vatAmount);
            invoice.setTotalAmount(totalAmount);

            // Pflichtangaben nach §14 UStG: Leistungsdatum, Absender, Empfänger und
            // Positionen — auch Grundlage der ZUGFeRD-Metadaten für die E-Rechnung.
            invoice.setDeliveryDate(currentJob.getDeliveryDate() != null ? currentJob.getDeliveryDate()
                    : java.time.LocalDate.now());
            applySenderData(invoice, user);
            applyRecipientData(invoice);
            invoice.setItems(buildInvoiceItems(vatRate));

            invoice.setPdfData(pdfBytes);

            // Finalisierung mit Audit-Eintrag und Eindeutigkeitsprüfung der Rechnungsnummer (R-07/R-11/R-36).
            CustomerInvoice savedInvoice = invoiceLifecycleService.createAndIssue(invoice,
                    "Rechnung erstellt aus Auftrag " + currentJob.getJobNumber());

            currentJob.setInvoiceId(savedInvoice.getId());
            jobRepository.save(currentJob);

            previewDialog.close();
            Notification.show(getTranslation("createinvoice.notification.saved", invoiceNumber), 4000,
                    Notification.Position.BOTTOM_END);

        } catch (InvoiceLifecycleException lifecycleEx) {
            log.warn("Lifecycle-Verstoß beim Speichern der Rechnung: {}", lifecycleEx.getMessage());
            Notification.show(lifecycleEx.getMessage(), 5000, Notification.Position.MIDDLE);
        } catch (Exception ex) {
            log.error("Fehler beim Speichern der Rechnung", ex);
            Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
                    Notification.Position.BOTTOM_END);
        }
    }

    /**
     * Absenderdaten aus dem Nutzerprofil und den Rechnungs-Stammdaten
     * (USt-IdNr./Steuernummer, Bankverbindung, Zahlungsbedingungen) übernehmen.
     */
    private void applySenderData(CustomerInvoice invoice, User user) {
        invoice.setSenderName(firstNonBlank(user.getCompany(),
                (safe(user.getFirstname()) + " " + safe(user.getName())).trim()));
        invoice.setSenderAddress((safe(user.getStreet()) + " " + safe(user.getHouseNumber())).trim());
        invoice.setSenderPostcode(safe(user.getZip()));
        invoice.setSenderCity(safe(user.getCity()));
        invoice.setSenderCountry("DE");
        invoice.setSenderPhone(safe(user.getPhone()));
        invoice.setSenderEmail(safe(user.getEmail()));

        userInvoiceDataService.findByUserId(user.getId()).ifPresent(invoiceData -> {
            invoice.setSenderVatId(safe(invoiceData.getUstId()));
            invoice.setSenderTaxNumber(safe(invoiceData.getTaxNumber()));
            invoice.setIban(safe(invoiceData.getIban()));
            invoice.setBankAccount(safe(invoiceData.getBankName()));
            invoice.setPaymentTerms(safe(invoiceData.getPaymentTerms()));
        });
        if (invoice.getPaymentDueDate() == null) {
            invoice.setPaymentDueDate(invoice.getInvoiceDate().plusDays(14));
        }
    }

    /**
     * Empfängerdaten aus der Kundenauswahl des Auftrags übernehmen; die volle
     * Anschrift und E-Mail kommen aus dem passenden Adressbuch-Eintrag.
     */
    private void applyRecipientData(CustomerInvoice invoice) {
        String customerSelection = currentJob.getCustomerSelection();
        if (customerSelection == null || customerSelection.isBlank()) {
            return;
        }
        // Format: "Firmenname | Vorname Nachname"
        String[] parts = customerSelection.split("\\|");
        String companyName = parts.length > 0 ? parts[0].trim() : "";
        String contactName = parts.length > 1 ? parts[1].trim() : "";

        invoice.setRecipientCompany(companyName);
        invoice.setRecipientName(firstNonBlank(contactName, companyName));

        Customer matchedCustomer = customerService.findAllForCurrentOwner().stream()
                .filter(c -> companyName.equalsIgnoreCase(c.getCompanyName())).findFirst().orElse(null);
        if (matchedCustomer != null) {
            invoice.setRecipientAddress(
                    (safe(matchedCustomer.getStreet()) + " " + safe(matchedCustomer.getHouseNumber())).trim());
            invoice.setRecipientPostcode(safe(matchedCustomer.getZip()));
            invoice.setRecipientCity(safe(matchedCustomer.getCity()));
            invoice.setRecipientCountry("DE");
            invoice.setRecipientEmail(safe(matchedCustomer.getMail()));
        }
    }

    /**
     * Rechnungspositionen aus den ausgewählten Leistungen aufbauen — mit
     * denselben Mengen-/Preisregeln wie {@link #calculateNetAmount()}, damit
     * die Positionssumme exakt zum Rechnungs-Netto passt.
     */
    private List<CustomerInvoiceItem> buildInvoiceItems(BigDecimal vatRate) {
        List<CustomerInvoiceItem> items = new ArrayList<>();
        for (ServiceRow row : getSelectedServices()) {
            Service service = row.getService();
            BigDecimal quantity = null;
            BigDecimal unitPrice = null;
            String unit = null;
            if (service.getCalculationBasis() == Service.CalculationBasis.FLAT_RATE && service.getPrice() != null) {
                quantity = BigDecimal.ONE;
                unitPrice = service.getPrice();
                unit = "pauschal";
            } else if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE
                    && service.getPricePerKilometer() != null && getRouteDistanceKm(row.getSelection()) != null) {
                quantity = BigDecimal.valueOf(getRouteDistanceKm(row.getSelection()));
                unitPrice = service.getPricePerKilometer();
                unit = "km";
            } else if (service.getCalculationBasis() == Service.CalculationBasis.TIME
                    && service.getPricePer15Minutes() != null && getTimeIn15MinUnits(row.getSelection()) != null) {
                quantity = new BigDecimal(getTimeIn15MinUnits(row.getSelection()));
                unitPrice = service.getPricePer15Minutes();
                unit = "x 15 Min.";
            }
            if (quantity == null || quantity.signum() <= 0 || unitPrice == null) {
                continue;
            }
            items.add(new CustomerInvoiceItem(quantity, unit, service.getName(), unitPrice, vatRate));
        }
        return items;
    }

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

    /**
     * Generates an invoice PDF from the template with actual job data.
     */
    private byte[] generateInvoicePdfFromTemplate(String templateData, User currentUser, String invoiceNumber)
            throws Exception {
        // Calculate totals
        BigDecimal netAmount = calculateNetAmount();
        BigDecimal vatRate = effectiveVatRate();
        BigDecimal vatAmount = netAmount.multiply(vatRate);
        BigDecimal totalAmount = netAmount.add(vatAmount);

        // Build variable substitution map
        Map<String, String> variables = new HashMap<>();

        // Master data (from user profile)
        variables.put("masterdata.company_name", safe(currentUser.getCompany()));
        variables.put("masterdata.contact_name", safe(currentUser.getFirstname()) + " " + safe(currentUser.getName()));
        variables.put("masterdata.street", safe(currentUser.getStreet()) + " " + safe(currentUser.getHouseNumber()));
        variables.put("masterdata.city", safe(currentUser.getZip()) + " " + safe(currentUser.getCity()));
        variables.put("masterdata.email", safe(currentUser.getEmail()));
        variables.put("masterdata.phone", safe(currentUser.getPhone()));

        // Customer data (from job - look up Customer entity for full address)
        String customerSelection = currentJob.getCustomerSelection();
        if (customerSelection != null && !customerSelection.isBlank()) {
            // Format: "Firmenname | Vorname Nachname"
            String[] parts = customerSelection.split("\\|");
            String companyName = parts.length > 0 ? parts[0].trim() : "";
            String contactName = parts.length > 1 ? parts[1].trim() : "";

            variables.put("customer.company_name", companyName);
            variables.put("customer.contact_name", contactName);

            // Look up full address from Customer entity
            Customer matchedCustomer = customerService.findAllForCurrentOwner().stream()
                    .filter(c -> companyName.equalsIgnoreCase(c.getCompanyName())).findFirst().orElse(null);

            if (matchedCustomer != null) {
                String street = safe(matchedCustomer.getStreet()) + " " + safe(matchedCustomer.getHouseNumber());
                String city = safe(matchedCustomer.getZip()) + " " + safe(matchedCustomer.getCity());
                variables.put("customer.street", street.trim());
                variables.put("customer.city", city.trim());
            } else {
                variables.put("customer.street", "");
                variables.put("customer.city", "");
            }
        } else {
            variables.put("customer.company_name", "");
            variables.put("customer.contact_name", "");
            variables.put("customer.street", "");
            variables.put("customer.city", "");
        }

        // Invoice data
        variables.put("invoice.number", invoiceNumber);
        variables.put("invoice_number", invoiceNumber);
        variables.put("masterdata.invoice_number", invoiceNumber);
        variables.put("invoice.date", java.time.LocalDate.now().toString());
        variables.put("invoice.net_total",
                netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
        variables.put("invoice.vat_total",
                vatAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
        variables.put("invoice.gross_total",
                totalAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
        variables.put("invoice.vat_rate",
                vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");

        // Job data
        if (currentJob.getRouteDistanceKm() != null) {
            variables.put("job.distance_km", String.format("%.1f", currentJob.getRouteDistanceKm()));
        } else {
            variables.put("job.distance_km", "-");
        }

        // Services data - add as JSON array for the template
        List<Map<String, String>> servicesData = new ArrayList<>();
        for (ServiceRow row : getSelectedServices()) {
            Service service = row.getService();
            Map<String, String> serviceData = new HashMap<>();
            serviceData.put("name", service.getName());

            // Calculate price based on calculation basis
            BigDecimal price = calculateServicePrice(row);
            if (price != null) {
                serviceData.put("netAmount", price.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ","));
            } else {
                serviceData.put("netAmount", "0,00");
            }

            servicesData.add(serviceData);
        }

        // Serialize services data to JSON and store in variables
        if (!servicesData.isEmpty()) {
            com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
            variables.put("services.json", mapper.writeValueAsString(servicesData));
            variables.put("services.count", String.valueOf(servicesData.size()));
        } else {
            variables.put("services.json", "[]");
            variables.put("services.count", "0");
        }

        // Generate PDF using CustomerInvoiceService
        return customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData, variables, currentUser);
    }

    private String safe(String value) {
        return value != null ? value : "";
    }

    private void showPdfPreviewDialog(byte[] pdfBytes, String templateData, User user) {
        String title = getTranslation("createinvoice.preview.title");

        Dialog pdfDialog = DialogStylingHelper.createStyledDialog(title, "90vw");
        pdfDialog.setHeight("90vh");

        IFrame pdfFrame = new IFrame();
        pdfFrame.setWidth("100%");
        pdfFrame.setHeight("100%");
        pdfFrame.addClassName("invoice-generator-frame");

        String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
        pdfFrame.getElement().setAttribute("src", "data:application/pdf;base64," + base64Pdf);

        Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
        closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

        Button saveButton = new Button(getTranslation("createinvoice.button.save"), e -> {
            Dialog confirm = DialogStylingHelper.createConfirmationDialog(
                    getTranslation("createinvoice.confirm.save.title"),
                    getTranslation("createinvoice.confirm.save.message"),
                    "560px",
                    getTranslation("button.cancel"),
                    getTranslation("createinvoice.confirm.save.confirm"),
                    () -> saveInvoice(templateData, user, pdfDialog),
                    ButtonVariant.LUMO_PRIMARY);
            confirm.open();
        });
        saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);

        pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
        pdfDialog.getFooter().add(closeButton, saveButton);
        pdfDialog.open();
    }

    public static void showSavedInvoiceDialog(byte[] pdfBytes, String invoiceNumber,
            com.vaadin.flow.component.Component parent) {
        String title = "Rechnung " + invoiceNumber;
        Dialog pdfDialog = DialogStylingHelper.createStyledDialog(title, "90vw");
        pdfDialog.setHeight("90vh");

        IFrame pdfFrame = new IFrame();
        pdfFrame.setWidth("100%");
        pdfFrame.setHeight("100%");
        pdfFrame.addClassName("invoice-generator-frame");

        String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
        pdfFrame.getElement().setAttribute("src", "data:application/pdf;base64," + base64Pdf);

        Button downloadButton = new Button("Herunterladen", e -> {
            parent.getElement()
                    .executeJs("const link = document.createElement('a');" + "link.href = 'data:application/pdf;base64,"
                            + base64Pdf + "';" + "link.download = '" + title.replaceAll("[^a-zA-Z0-9\\-]", "_")
                            + ".pdf';" + "link.click();");
        });
        downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

        Button closeButton = new Button("Schließen", e -> pdfDialog.close());
        closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
        pdfDialog.getFooter().add(downloadButton, closeButton);
        pdfDialog.open();
    }

    private String formatDuration(int durationSeconds) {
        int hours = durationSeconds / 3600;
        int minutes = (durationSeconds % 3600) / 60;

        if (hours > 0) {
            return String.format("%d Std. %d Min.", hours, minutes);
        } else {
            return String.format("%d Min.", minutes);
        }
    }

    @Override
    public String getPageTitle() {
        return getTranslation("page.title.invoice.create");
    }
}
