package de.assecutor.votianlt.service;

import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.invoices.CustomerInvoiceData;
import de.assecutor.votianlt.model.invoices.CustomerInvoiceItem;
import org.springframework.stereotype.Service;

import com.itextpdf.html2pdf.HtmlConverter;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.time.LocalDate;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;

@Service
public class CustomerInvoiceService {

    public CustomerInvoice createCustomerInvoice(String customerId, String jobId) {
        CustomerInvoice invoice = new CustomerInvoice();

        // Set basic invoice details
        invoice.setInvoiceNumber("INV-" + System.currentTimeMillis());
        invoice.setInvoiceDate(LocalDate.now());
        invoice.setDeliveryDate(LocalDate.now());
        invoice.setDescription("Logistikdienstleistungen für Auftrag " + jobId);

        // Set sender details (your company)
        invoice.setSenderName("Assecutor GmbH");
        invoice.setSenderAddress("Hauptstraße 456");
        invoice.setSenderPostcode("12345");
        invoice.setSenderCity("Berlin");
        invoice.setSenderCountry("Deutschland");
        invoice.setSenderTaxNumber("12/345/67890");
        invoice.setSenderVatId("DE123456789");
        invoice.setSenderPhone("+49 30 12345678");
        invoice.setSenderEmail("info@assecutor.de");
        invoice.setSenderWebsite("https://www.assecutor.de");

        // Payment terms
        invoice.setPaymentTerms("Zahlbar innerhalb von 14 Tagen netto ohne Abzug.");
        invoice.setPaymentDueDate(LocalDate.now().plusDays(14));
        invoice.setIban("DE89 3704 0044 0532 0130 00");
        invoice.setBic("COBADEFFXXX");

        // Legal information
        invoice.setLegalNotes("Geschäftsführer: Max Mustermann | Registergericht: Berlin | HRB 123456");

        return invoice;
    }

    public CustomerInvoiceData createCustomerInvoiceData(String customerId, String jobId) {
        CustomerInvoiceData invoiceData = new CustomerInvoiceData();

        // Rechnungsdetails
        invoiceData.setInvoiceNumber("INV-" + System.currentTimeMillis());
        invoiceData.setInvoiceDate(LocalDate.now());
        invoiceData.setDeliveryDate(LocalDate.now());
        invoiceData.setDescription("Logistikdienstleistungen für Auftrag " + jobId);

        // Rechnungssteller (vollständige Angaben nach deutschem Recht)
        invoiceData.setSenderName("Assecutor GmbH");
        invoiceData.setSenderAddress("Hauptstraße 456");
        invoiceData.setSenderPostcode("12345");
        invoiceData.setSenderCity("Berlin");
        invoiceData.setSenderCountry("Deutschland");
        invoiceData.setSenderTaxNumber("12/345/67890");
        invoiceData.setSenderVatId("DE123456789");
        invoiceData.setSenderPhone("+49 30 12345678");
        invoiceData.setSenderEmail("info@assecutor.de");
        invoiceData.setSenderWebsite("https://www.assecutor.de");

        // Rechnungsempfänger
        invoiceData.setRecipientName("Max Mustermann");
        invoiceData.setRecipientCompany("Musterfirma GmbH");
        invoiceData.setRecipientAddress("Musterstraße 123");
        invoiceData.setRecipientPostcode("54321");
        invoiceData.setRecipientCity("Hamburg");
        invoiceData.setRecipientCountry("Deutschland");
        invoiceData.setRecipientVatId("DE987654321");

        // Rechnungsposten
        List<CustomerInvoiceItem> items = new ArrayList<>();
        BigDecimal vatRate = new BigDecimal("0.19"); // 19% USt.

        CustomerInvoiceItem item1 = new CustomerInvoiceItem(new BigDecimal("2"), "Std.", "Transportdienstleistung",
                new BigDecimal("85.00"), vatRate);
        CustomerInvoiceItem item2 = new CustomerInvoiceItem(new BigDecimal("1"), "Stk.", "Logistikkoordination",
                new BigDecimal("120.00"), vatRate);
        CustomerInvoiceItem item3 = new CustomerInvoiceItem(new BigDecimal("50"), "km", "Kilometergebühr",
                new BigDecimal("0.60"), vatRate);

        items.add(item1);
        items.add(item2);
        items.add(item3);
        invoiceData.setItems(items);

        // Beträge berechnen
        BigDecimal netAmount = items.stream().map(CustomerInvoiceItem::getNetTotal).reduce(BigDecimal.ZERO,
                BigDecimal::add);
        BigDecimal vatAmount = netAmount.multiply(vatRate);
        BigDecimal totalAmount = netAmount.add(vatAmount);

        invoiceData.setNetAmount(netAmount);
        invoiceData.setVatRate(vatRate);
        invoiceData.setVatAmount(vatAmount);
        invoiceData.setTotalAmount(totalAmount);

        // Zahlungsdetails
        invoiceData.setPaymentTerms("Zahlbar innerhalb von 14 Tagen netto ohne Abzug.");
        invoiceData.setPaymentDueDate(LocalDate.now().plusDays(14));
        invoiceData.setBankAccount("Assecutor GmbH");
        invoiceData.setIban("DE89 3704 0044 0532 0130 00");
        invoiceData.setBic("COBADEFFXXX");

        // Rechtliche Hinweise
        invoiceData.setLegalNotes("Geschäftsführer: Max Mustermann | Registergericht: Berlin | HRB 123456");

        return invoiceData;
    }

    public byte[] generateCustomerInvoicePdf() throws Exception {
        // Backward-compatible sample generation
        CustomerInvoiceData sampleData = createCustomerInvoiceData("customerId", "jobId");
        return generateCustomerInvoicePdf(sampleData);
    }

    /**
     * Generate a Customer Invoice PDF from provided data using the HTML template.
     */
    public byte[] generateCustomerInvoicePdf(CustomerInvoiceData data) throws Exception {
        // Read the HTML template
        String htmlContent = readCustomerInvoiceHtmlTemplate();

        // Fill HTML with provided data
        String filledHtml = fillCustomerInvoiceHtmlWithInvoiceData(htmlContent, data);

        // Generate PDF from HTML
        return generatePdfFromHtmlString(filledHtml);
    }

    private String readCustomerInvoiceHtmlTemplate() throws Exception {
        // Read the HTML template file from classpath (works in Docker/production)
        try (java.io.InputStream inputStream = getClass().getClassLoader()
                .getResourceAsStream("templates/customer_invoice.html")) {
            if (inputStream == null) {
                throw new java.io.IOException("Template file not found: templates/customer_invoice.html");
            }
            return new String(inputStream.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
        }
    }

    private String fillCustomerInvoiceHtmlWithInvoiceData(String html, CustomerInvoiceData data) {
        String filledHtml = html;
        // Replace invoice header data
        filledHtml = filledHtml.replace("${invoiceData.invoiceNumber}", nvl(data.getInvoiceNumber()));
        filledHtml = filledHtml.replace("${invoiceData.invoiceDate}", nvl(data.getFormattedInvoiceDate()));
        filledHtml = filledHtml.replace("${invoiceData.deliveryDate}", nvl(data.getFormattedDeliveryDate()));
        filledHtml = filledHtml.replace("${invoiceData.description}", nvl(data.getDescription()));

        // Replace sender details (complete legal information)
        filledHtml = filledHtml.replace("${invoiceData.senderName}", nvl(data.getSenderName()));
        filledHtml = filledHtml.replace("${invoiceData.senderAddress}", nvl(data.getSenderAddress()));
        filledHtml = filledHtml.replace("${invoiceData.senderPostcode}", nvl(data.getSenderPostcode()));
        filledHtml = filledHtml.replace("${invoiceData.senderCity}", nvl(data.getSenderCity()));
        filledHtml = filledHtml.replace("${invoiceData.senderCountry}", nvl(data.getSenderCountry()));
        filledHtml = filledHtml.replace("${invoiceData.senderTaxNumber}", nvl(data.getSenderTaxNumber()));
        filledHtml = filledHtml.replace("${invoiceData.senderVatId}", nvl(data.getSenderVatId()));
        filledHtml = filledHtml.replace("${invoiceData.senderPhone}", nvl(data.getSenderPhone()));
        filledHtml = filledHtml.replace("${invoiceData.senderEmail}", nvl(data.getSenderEmail()));
        filledHtml = filledHtml.replace("${invoiceData.senderWebsite}", nvl(data.getSenderWebsite()));

        // Replace recipient details
        filledHtml = filledHtml.replace("${invoiceData.recipientName}", nvl(data.getRecipientName()));
        filledHtml = filledHtml.replace("${invoiceData.recipientCompany}", nvl(data.getRecipientCompany()));
        filledHtml = filledHtml.replace("${invoiceData.recipientAddress}", nvl(data.getRecipientAddress()));
        filledHtml = filledHtml.replace("${invoiceData.recipientPostcode}", nvl(data.getRecipientPostcode()));
        filledHtml = filledHtml.replace("${invoiceData.recipientCity}", nvl(data.getRecipientCity()));
        filledHtml = filledHtml.replace("${invoiceData.recipientCountry}", nvl(data.getRecipientCountry()));
        filledHtml = filledHtml.replace("${invoiceData.recipientVatId}", nvl(data.getRecipientVatId()));

        // Replace invoice items
        if (data.getItems() != null && !data.getItems().isEmpty()) {
            StringBuilder itemRows = new StringBuilder();
            for (CustomerInvoiceItem item : data.getItems()) {
                itemRows.append("<tr>");
                itemRows.append("<td style='text-align: center;'>").append(formatDecimal(item.getQuantity()))
                        .append(" ").append(nvl(item.getUnit())).append("</td>");
                itemRows.append("<td>").append(nvl(item.getDescription())).append("</td>");
                itemRows.append("<td style='text-align: right;'>").append(formatCurrency(item.getUnitPrice()))
                        .append("</td>");
                itemRows.append("<td style='text-align: right;'>").append(formatCurrency(item.getNetTotal()))
                        .append("</td>");
                itemRows.append("</tr>");
            }
            filledHtml = filledHtml.replace("<!-- ITEM_ROWS -->", itemRows.toString());
        }

        // Replace amounts
        filledHtml = filledHtml.replace("${invoiceData.netAmount}", nvl(data.getFormattedNetAmount()));
        filledHtml = filledHtml.replace("${invoiceData.vatRate}", nvl(data.getFormattedVatRate()));
        filledHtml = filledHtml.replace("${invoiceData.vatAmount}", nvl(data.getFormattedVatAmount()));
        filledHtml = filledHtml.replace("${invoiceData.totalAmount}", nvl(data.getFormattedTotalAmount()));

        // Replace payment details
        filledHtml = filledHtml.replace("${invoiceData.paymentTerms}", nvl(data.getPaymentTerms()));
        filledHtml = filledHtml.replace("${invoiceData.paymentDueDate}", nvl(data.getFormattedPaymentDueDate()));
        filledHtml = filledHtml.replace("${invoiceData.bankAccount}", nvl(data.getBankAccount()));
        filledHtml = filledHtml.replace("${invoiceData.iban}", nvl(data.getIban()));
        filledHtml = filledHtml.replace("${invoiceData.bic}", nvl(data.getBic()));

        // Replace legal notes
        filledHtml = filledHtml.replace("${invoiceData.legalNotes}", nvl(data.getLegalNotes()));
        filledHtml = filledHtml.replace("${invoiceData.reverseChargeNote}", nvl(data.getReverseChargeNote()));

        return filledHtml;
    }

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

    private String formatCurrency(BigDecimal amount) {
        if (amount == null)
            return "0,00 €";
        return NumberFormat.getCurrencyInstance(Locale.GERMANY).format(amount);
    }

    private String formatDecimal(BigDecimal value) {
        if (value == null)
            return "0";
        return NumberFormat.getNumberInstance(Locale.GERMANY).format(value);
    }

    private byte[] generatePdfFromHtmlString(String htmlContent) throws Exception {
        // Create PDF using iText library
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // Convert HTML to PDF using iText 8 HtmlConverter
        HtmlConverter.convertToPdf(htmlContent, baos);

        return baos.toByteArray();
    }

    /**
     * Generate a PDF preview from canvas template data. Creates an HTML
     * representation of the canvas elements and converts it to PDF.
     */
    public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData) throws Exception {
        return generatePdfFromCanvasTemplate(jsonTemplateData, (de.assecutor.votianlt.model.User) null, null);
    }

    public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, de.assecutor.votianlt.model.User user)
            throws Exception {
        return generatePdfFromCanvasTemplate(jsonTemplateData, user, null);
    }

    public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, de.assecutor.votianlt.model.User user,
            String invoicePrefix) throws Exception {
        return generatePdfFromCanvasTemplate(jsonTemplateData, user, invoicePrefix, null);
    }

    public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, de.assecutor.votianlt.model.User user,
            String invoicePrefix, BigDecimal vatRate) throws Exception {
        BigDecimal effectiveVatRate = vatRate != null ? vatRate
                : (user != null && user.getVatRate() != null ? user.getVatRate() : new BigDecimal("0.19"));

        // Prepare variable substitution map
        java.util.Map<String, String> variables = new java.util.HashMap<>();

        if (user != null) {
            // Use actual user data
            String company = user.getCompany();
            variables.put("masterdata.company_name", (company != null && !company.isEmpty()) ? company : "Ihre Firma");
            variables.put("masterdata.contact_name", safe(user.getFirstname()) + " " + safe(user.getName()));
            variables.put("masterdata.street", safe(user.getStreet()) + " " + safe(user.getHouseNumber()));
            variables.put("masterdata.city", safe(user.getZip()) + " " + safe(user.getCity()));
            variables.put("masterdata.email", safe(user.getEmail()));
            variables.put("masterdata.phone", safe(user.getPhone()));
            variables.put("masterdata.invoice_number",
                    (invoicePrefix != null && !invoicePrefix.isBlank() ? invoicePrefix : "") + "12345");
        } else {
            // Default values for preview without user
            variables.put("masterdata.company_name", "Meine Firma GmbH");
            variables.put("masterdata.contact_name", "Max Mustermann");
            variables.put("masterdata.street", "Musterstraße 123");
            variables.put("masterdata.city", "12345 Musterstadt");
            variables.put("masterdata.email", "kontakt@firma.de");
            variables.put("masterdata.phone", "0123 456789");
            variables.put("masterdata.invoice_number",
                    (invoicePrefix != null && !invoicePrefix.isBlank() ? invoicePrefix : "") + "12345");
        }

        // Customer data (placeholder for now - would come from job/customer selection)
        variables.put("customer.company_name", "Kundenfirma GmbH");
        variables.put("customer.contact_name", "Erika Mustermann");
        variables.put("customer.street", "Kundenstraße 456");
        variables.put("customer.city", "54321 Kundenstadt");
        variables.put("customer.email", "kunde@beispiel.de");
        variables.put("customer.phone", "0987 654321");

        return generatePdfFromCanvasTemplate(jsonTemplateData, variables, effectiveVatRate);
    }

    /**
     * Render a canvas template PDF using a pre-built variable substitution map.
     * Used e.g. for the system invoice template where the issuer data comes from
     * SystemInvoiceData instead of a User.
     */
    public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, java.util.Map<String, String> variables,
            BigDecimal vatRate) throws Exception {
        BigDecimal effectiveVatRate = vatRate != null ? vatRate : new BigDecimal("0.19");
        // Parse the JSON template data
        com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
        com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonTemplateData);
        com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");

        // Build HTML content from canvas elements
        StringBuilder htmlBuilder = new StringBuilder();
        htmlBuilder.append("<!DOCTYPE html>");
        htmlBuilder.append("<html><head>");
        htmlBuilder.append("<meta charset='UTF-8'>");
        htmlBuilder.append("<style>");
        htmlBuilder.append("@page { size: A4; margin: 0; }");
        htmlBuilder.append(
                "body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
        htmlBuilder.append(".element { position: absolute; box-sizing: border-box; overflow: hidden; }");
        htmlBuilder.append(".text { white-space: nowrap; overflow: visible; }");
        htmlBuilder.append(".line { border-top: 1px solid #333; }");
        htmlBuilder.append(".image { overflow: hidden; }");
        htmlBuilder.append(".image img { width: 100%; height: 100%; object-fit: contain; display: block; }");
        htmlBuilder.append("</style>");
        htmlBuilder.append("</head><body>");

        if (elements != null && elements.isArray()) {
            for (com.fasterxml.jackson.databind.JsonNode element : elements) {
                String type = element.has("type") ? element.get("type").asText("text") : "text";
                String variable = element.has("variable") ? element.get("variable").asText(null) : null;
                // For static elements with variables, use the variable to get the text
                // Otherwise use the stored text
                String text;
                if (variable != null && !variable.isEmpty()) {
                    text = ""; // Will be replaced by variable value below
                } else {
                    text = element.has("text") ? element.get("text").asText("") : "";
                }

                // Use percentage values if available, otherwise fall back to legacy pixel
                // values
                double xPercent, yPercent, widthPercent, heightPercent;
                if (element.has("xPercent")) {
                    xPercent = element.get("xPercent").asDouble(0);
                } else {
                    // Legacy: convert pixels to percent
                    double x = element.get("x").asDouble(0);
                    xPercent = x / 595.0 * 100;
                }
                if (element.has("yPercent")) {
                    yPercent = element.get("yPercent").asDouble(0);
                } else {
                    double y = element.get("y").asDouble(0);
                    yPercent = y / 842.0 * 100;
                }
                if (element.has("widthPercent")) {
                    widthPercent = element.get("widthPercent").asDouble(15);
                } else {
                    double width = element.get("width").asDouble(150);
                    widthPercent = width / 595.0 * 100;
                }
                if (element.has("heightPercent")) {
                    heightPercent = element.get("heightPercent").asDouble(3);
                } else {
                    double height = element.get("height").asDouble(30);
                    heightPercent = height / 842.0 * 100;
                }

                int fontSize = element.has("fontSize") ? element.get("fontSize").asInt(14) : 14;
                String color = element.has("color") ? element.get("color").asText("#333333") : "#333333";
                String textAlign = element.has("textAlign") ? element.get("textAlign").asText("left") : "left";

                // Convert percentages to mm (A4 is 210mm x 297mm)
                double mmX = xPercent / 100.0 * 210.0;
                double mmY = yPercent / 100.0 * 297.0;
                double mmWidth = widthPercent / 100.0 * 210.0;
                double mmHeight = heightPercent / 100.0 * 297.0;

                htmlBuilder.append("<div class='element ").append(type).append("' ");
                htmlBuilder.append("style='");
                htmlBuilder.append("left:").append(String.format(java.util.Locale.US, "%.2f", mmX)).append("mm;");
                htmlBuilder.append("top:").append(String.format(java.util.Locale.US, "%.2f", mmY)).append("mm;");
                if ("line".equals(type)) {
                    htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth))
                            .append("mm;");
                    htmlBuilder.append("height:0;border-top:1px solid #333;");
                } else if ("vline".equals(type)) {
                    htmlBuilder.append("width:0;border-left:1px solid #333;");
                    htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight))
                            .append("mm;");
                } else {
                    htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth))
                            .append("mm;");
                    htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight))
                            .append("mm;");
                    htmlBuilder.append("font-size:").append(fontSize).append("pt;");
                    htmlBuilder.append("line-height:")
                            .append(String.format(java.util.Locale.US, "%.2f", fontSize * 1.2)).append("pt;");
                    htmlBuilder.append("color:").append(color).append(";");
                    // For services.list use block display to allow table to fill width
                    if ("services.list".equals(variable)) {
                        htmlBuilder.append("display:block;overflow:visible;padding:0;");
                    } else {
                        // Vertically center content for other elements
                        htmlBuilder.append("display:flex;align-items:center;");
                        switch (textAlign) {
                        case "center":
                            htmlBuilder.append("justify-content:center;text-align:center;");
                            break;
                        case "right":
                            htmlBuilder.append("justify-content:flex-end;text-align:right;");
                            break;
                        default:
                            htmlBuilder.append("justify-content:flex-start;text-align:left;");
                            break;
                        }
                    }
                }
                htmlBuilder.append("'");
                htmlBuilder.append(">");

                // Replace variables with actual values FIRST
                if (variable != null && variables.containsKey(variable)) {
                    text = variables.get(variable);
                } else if (text != null && text.startsWith("{{")) {
                    // Handle variable placeholder format {{variable}}|Display Text or {{variable}}
                    int pipeIndex = text.indexOf('|');
                    String placeholderPart;
                    if (pipeIndex > -1) {
                        placeholderPart = text.substring(0, pipeIndex);
                    } else {
                        placeholderPart = text;
                    }
                    if (placeholderPart.startsWith("{{") && placeholderPart.endsWith("}}")) {
                        String varName = placeholderPart.substring(2, placeholderPart.length() - 2);
                        if (variables.containsKey(varName)) {
                            text = variables.get(varName);
                        }
                    }
                }

                // Escape HTML special characters in text AFTER variable replacement
                if (text != null) {
                    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
                            .replace("'", "&#x27;");
                    // Convert newlines to <br> for multi-line elements (e.g. customer address)
                    text = text.replace("\n", "<br>");
                } else {
                    text = "";
                }

                if ("image".equals(type)) {
                    if (element.has("imageData") && !element.get("imageData").asText().isEmpty()) {
                        String imageData = element.get("imageData").asText();
                        // Ensure proper data URL format
                        if (!imageData.startsWith("data:")) {
                            imageData = "data:image/png;base64," + imageData;
                        }
                        // Use object-fit:contain to preserve aspect ratio
                        htmlBuilder.append(
                                "<div style='width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;'>");
                        htmlBuilder.append("<img src=\"").append(imageData.replace("\"", "%22"))
                                .append("\" style='max-width:100%;max-height:100%;object-fit:contain;' alt='Bild' />");
                        htmlBuilder.append("</div>");
                    } else {
                        htmlBuilder.append(
                                "<div style='width:100%;height:100%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;font-size:10pt;color:#666;'>[Bild]</div>");
                    }
                } else if ("services.list".equals(variable)) {
                    // Render positions from services.json if provided (e.g. system
                    // invoice: price table positions), otherwise sample data
                    if (variables.containsKey("services.json")) {
                        htmlBuilder.append(generateServicesTableHtmlWithData(mmWidth, variables));
                    } else {
                        htmlBuilder.append(generateServicesTableHtml(mmWidth, effectiveVatRate));
                    }
                } else if (text.contains("<br>")) {
                    // Multi-line text: render without nowrap so <br> tags work
                    htmlBuilder.append("<span>").append(text).append("</span>");
                } else {
                    // Wrap text in a span to prevent flexbox issues
                    htmlBuilder.append("<span style='white-space:nowrap;'>").append(text).append("</span>");
                }

                htmlBuilder.append("</div>");
            }
        }

        htmlBuilder.append("</body></html>");

        // Generate PDF from HTML
        return generatePdfFromHtmlString(htmlBuilder.toString());
    }

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

    /**
     * Generate HTML table for services list with summary section below.
     */
    private String generateServicesTableHtml(double widthMm, BigDecimal vatRate) {
        StringBuilder html = new StringBuilder();

        BigDecimal pct = vatRate.multiply(new BigDecimal("100")).setScale(2, java.math.RoundingMode.HALF_UP)
                .stripTrailingZeros();
        if (pct.scale() < 0) {
            pct = pct.setScale(0);
        }
        String vatLabel = pct.toPlainString().replace('.', ',') + "%";

        // Sample data for preview (will be replaced with actual job data later)
        String[][] sampleData = { { "Umzugsleistung inkl. Verpackung", vatLabel, "450,00 €" },
                { "Entsorgung Möbel", vatLabel, "85,00 €" }, { "Montage/De-Montage", vatLabel, "120,00 €" } };

        // Calculate totals
        double netTotal = 655.00;
        double grossTotal = netTotal + (netTotal * vatRate.doubleValue());

        // Wrapper div
        html.append("<div style='width:100%;box-sizing:border-box;'>");

        // Table
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");

        // Header row
        html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
        html.append(
                "<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
        html.append(
                "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
        html.append(
                "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
        html.append("</tr>");

        // Data rows
        for (int i = 0; i < sampleData.length; i++) {
            String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
            html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
            html.append(
                    "<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>")
                    .append(sampleData[i][0]).append("</td>");
            html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1])
                    .append("</td>");
            html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2])
                    .append("</td>");
            html.append("</tr>");
        }

        html.append("</table>");

        // Summary section
        html.append("<div style='margin-top:8px;width:100%;'>");

        // Summary table for alignment with proper column separation
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");

        // Nettosumme - label in col 2, value in col 3
        html.append("<tr>");
        html.append("<td style='width:55%;padding:2px 0;'></td>");
        html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
        html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
                .append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
        html.append("</tr>");

        // Gesamtsumme - label in col 2, value in col 3
        html.append("<tr>");
        html.append("<td style='width:55%;padding:2px 0;'></td>");
        html.append(
                "<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
        html.append(
                "<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
                .append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
        html.append("</tr>");

        html.append("</table>");
        html.append("</div>");
        html.append("</div>");

        return html.toString();
    }

    /**
     * Generate a PDF preview from canvas template data with pre-built variables
     * map. This version accepts a pre-built variables map instead of building it
     * from the User object.
     */
    public byte[] generatePdfFromCanvasTemplateWithData(String jsonTemplateData,
            java.util.Map<String, String> variables, de.assecutor.votianlt.model.User user) throws Exception {

        System.out.println("DEBUG: generatePdfFromCanvasTemplateWithData called");
        System.out.println("DEBUG: templateData length: " + (jsonTemplateData != null ? jsonTemplateData.length() : 0));

        // Parse the JSON template data - JSON is stored as a string in the database
        com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();

        // The template data is stored as a JSON string, so we need to parse it as a
        // string first
        String jsonContent = mapper.readValue(jsonTemplateData, String.class);
        com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonContent);

        System.out.println("DEBUG: rootNode has 'elements': " + rootNode.has("elements"));

        com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");

        System.out.println("DEBUG: elements isArray: " + (elements != null ? elements.isArray() : false));
        if (elements != null && !elements.isMissingNode()) {
            System.out.println("DEBUG: elements nodeType: " + elements.getNodeType());
            System.out.println("DEBUG: elements size: " + elements.size());
        }

        // Build HTML content from canvas elements
        StringBuilder htmlBuilder = new StringBuilder();
        htmlBuilder.append("<!DOCTYPE html>");
        htmlBuilder.append("<html><head>");
        htmlBuilder.append("<meta charset='UTF-8'>");
        htmlBuilder.append("<style>");
        htmlBuilder.append("@page { size: A4; margin: 0; }");
        htmlBuilder.append(
                "body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
        htmlBuilder.append(".element { position: absolute; box-sizing: border-box; overflow: hidden; }");
        htmlBuilder.append(".text { white-space: nowrap; overflow: visible; }");
        htmlBuilder.append(".line { border-top: 1px solid #333; }");
        htmlBuilder.append(".image { overflow: hidden; }");
        htmlBuilder.append(".image img { width: 100%; height: 100%; object-fit: contain; display: block; }");
        htmlBuilder.append("</style>");
        htmlBuilder.append("</head><body>");

        // Handle elements - could be an array or an object with element IDs as keys
        if (elements != null && !elements.isMissingNode() && !elements.isNull()) {
            int elementCount = 0;

            // Convert elements to a list we can iterate over
            java.util.List<com.fasterxml.jackson.databind.JsonNode> elementList = new java.util.ArrayList<>();

            if (elements.isArray()) {
                // Already an array - iterate directly
                elements.forEach(elementList::add);
            } else if (elements.isObject()) {
                // Object with element IDs as keys - extract values
                System.out.println("DEBUG: Converting object to array");
                elements.fields().forEachRemaining(entry -> elementList.add(entry.getValue()));
            } else {
                System.out.println("DEBUG: Unexpected elements type: " + elements.getNodeType());
            }

            for (com.fasterxml.jackson.databind.JsonNode element : elementList) {
                elementCount++;
                String type = element.has("type") ? element.get("type").asText("text") : "text";
                String variable = element.has("variable") ? element.get("variable").asText(null) : null;
                String text = "";

                System.out.println(
                        "DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);

                // Use percentage values if available, otherwise fall back to legacy pixel
                // values
                double xPercent, yPercent, widthPercent, heightPercent;
                if (element.has("xPercent")) {
                    xPercent = element.get("xPercent").asDouble(0);
                } else {
                    double x = element.get("x").asDouble(0);
                    xPercent = x / 595.0 * 100;
                }
                if (element.has("yPercent")) {
                    yPercent = element.get("yPercent").asDouble(0);
                } else {
                    double y = element.get("y").asDouble(0);
                    yPercent = y / 842.0 * 100;
                }
                if (element.has("widthPercent")) {
                    widthPercent = element.get("widthPercent").asDouble(15);
                } else {
                    double width = element.get("width").asDouble(150);
                    widthPercent = width / 595.0 * 100;
                }
                if (element.has("heightPercent")) {
                    heightPercent = element.get("heightPercent").asDouble(3);
                } else {
                    double height = element.get("height").asDouble(30);
                    heightPercent = height / 842.0 * 100;
                }

                int fontSize = element.has("fontSize") ? element.get("fontSize").asInt(14) : 14;
                String color = element.has("color") ? element.get("color").asText("#333333") : "#333333";
                String textAlign = element.has("textAlign") ? element.get("textAlign").asText("left") : "left";

                // Convert percentages to mm (A4 is 210mm x 297mm)
                double mmX = xPercent / 100.0 * 210.0;
                double mmY = yPercent / 100.0 * 297.0;
                double mmWidth = widthPercent / 100.0 * 210.0;
                double mmHeight = heightPercent / 100.0 * 297.0;

                htmlBuilder.append("<div class='element ").append(type).append("' ");
                htmlBuilder.append("style='");
                htmlBuilder.append("left:").append(String.format(java.util.Locale.US, "%.2f", mmX)).append("mm;");
                htmlBuilder.append("top:").append(String.format(java.util.Locale.US, "%.2f", mmY)).append("mm;");
                if ("line".equals(type)) {
                    htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth))
                            .append("mm;");
                    htmlBuilder.append("height:0;border-top:1px solid #333;");
                } else if ("vline".equals(type)) {
                    htmlBuilder.append("width:0;border-left:1px solid #333;");
                    htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight))
                            .append("mm;");
                } else {
                    htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth))
                            .append("mm;");
                    htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight))
                            .append("mm;");
                    htmlBuilder.append("font-size:").append(fontSize).append("pt;");
                    htmlBuilder.append("line-height:")
                            .append(String.format(java.util.Locale.US, "%.2f", fontSize * 1.2)).append("pt;");
                    htmlBuilder.append("color:").append(color).append(";");
                    // For services.list use block display
                    if ("services.list".equals(variable)) {
                        htmlBuilder.append("display:block;overflow:visible;padding:0;");
                    } else {
                        htmlBuilder.append("display:flex;align-items:center;");
                        switch (textAlign) {
                        case "center":
                            htmlBuilder.append("justify-content:center;text-align:center;");
                            break;
                        case "right":
                            htmlBuilder.append("justify-content:flex-end;text-align:right;");
                            break;
                        default:
                            htmlBuilder.append("justify-content:flex-start;text-align:left;");
                            break;
                        }
                    }
                }
                htmlBuilder.append("'");
                htmlBuilder.append(">");

                // Get text from element or replace variable
                if (element.has("text") && !element.get("text").asText("").isEmpty()) {
                    text = element.get("text").asText("");
                }

                // Replace variables with actual values
                if (variable != null && variables.containsKey(variable)) {
                    text = variables.get(variable);
                    System.out.println("DEBUG: Replaced variable " + variable + " with: " + text);
                } else if (variable != null) {
                    // Variable exists but no value provided - use placeholder
                    text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "")
                            .replace("job.", "") + "]";
                    System.out.println("DEBUG: No value for variable " + variable + ", using placeholder");
                } else if ("customer".equals(type)) {
                    // Customer-type element without variable: compose address from customer
                    // variables
                    String line1 = variables.getOrDefault("customer.company_name",
                            variables.getOrDefault("customer.contact_name", ""));
                    String line2 = variables.getOrDefault("customer.street", "");
                    String line3 = variables.getOrDefault("customer.city", "");
                    text = line1 + "\n" + line2 + "\n" + line3;
                    System.out.println("DEBUG: Customer element - composed address: " + text);
                } else {
                    System.out.println("DEBUG: Using static text: " + text);
                }

                // Escape HTML special characters
                if (text != null) {
                    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
                            .replace("'", "&#x27;");
                    // Convert newlines to <br> for multi-line elements (e.g. customer address)
                    text = text.replace("\n", "<br>");
                } else {
                    text = "";
                }

                if ("image".equals(type)) {
                    if (element.has("imageData") && !element.get("imageData").asText().isEmpty()) {
                        String imageData = element.get("imageData").asText();
                        if (!imageData.startsWith("data:")) {
                            imageData = "data:image/png;base64," + imageData;
                        }
                        htmlBuilder.append(
                                "<div style='width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;'>");
                        htmlBuilder.append("<img src=\"").append(imageData.replace("\"", "%22"))
                                .append("\" style='max-width:100%;max-height:100%;object-fit:contain;' alt='Bild' />");
                        htmlBuilder.append("</div>");
                    } else {
                        htmlBuilder.append(
                                "<div style='width:100%;height:100%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;font-size:10pt;color:#666;'>[Bild]</div>");
                    }
                } else if ("services.list".equals(variable)) {
                    // Render services list as a table with actual data
                    htmlBuilder.append(generateServicesTableHtmlWithData(mmWidth, variables));
                } else if (text.contains("<br>")) {
                    // Multi-line text: render without nowrap so <br> tags work
                    htmlBuilder.append("<span>").append(text).append("</span>");
                } else {
                    htmlBuilder.append("<span style='white-space:nowrap;'>").append(text).append("</span>");
                }

                htmlBuilder.append("</div>");
            }
            System.out.println("DEBUG: Processed " + elementCount + " elements");
        } else {
            System.out.println("DEBUG: No elements found in template!");
        }

        htmlBuilder.append("</body></html>");

        String htmlOutput = htmlBuilder.toString();
        System.out.println("DEBUG: Generated HTML length: " + htmlOutput.length());
        System.out.println("DEBUG: HTML preview: " + htmlOutput.substring(0, Math.min(500, htmlOutput.length())));

        return generatePdfFromHtmlString(htmlOutput);
    }

    /**
     * Generate HTML table for services list with actual data from variables.
     */
    private String generateServicesTableHtmlWithData(double widthMm, java.util.Map<String, String> variables) {
        StringBuilder html = new StringBuilder();

        // Get invoice data from variables
        String netTotal = variables.getOrDefault("invoice.net_total", "0,00 €");
        String vatTotal = variables.getOrDefault("invoice.vat_total", "0,00 €");
        String grossTotal = variables.getOrDefault("invoice.gross_total", "0,00 €");
        String vatRateLabel = variables.getOrDefault("invoice.vat_rate", "19%");

        // Parse services JSON from variables
        java.util.List<java.util.Map<String, String>> servicesData = new java.util.ArrayList<>();
        String servicesJson = variables.get("services.json");
        if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
            try {
                com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
                com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef = new com.fasterxml.jackson.core.type.TypeReference<>() {
                };
                servicesData = mapper.readValue(servicesJson, typeRef);
            } catch (Exception e) {
                System.err.println("DEBUG: Failed to parse services JSON: " + e.getMessage());
            }
        }

        // Wrapper div
        html.append("<div style='width:100%;box-sizing:border-box;'>");

        // Table
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");

        // Header row
        html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
        html.append(
                "<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
        html.append(
                "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
        html.append(
                "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
        html.append("</tr>");

        // Data rows - use actual service data from the job
        if (servicesData.isEmpty()) {
            // Fallback: show a single row with no data
            html.append("<tr style='border-bottom:1px solid #eeeeee;'>");
            html.append(
                    "<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
            html.append("</tr>");
        } else {
            for (int i = 0; i < servicesData.size(); i++) {
                java.util.Map<String, String> service = servicesData.get(i);
                String name = service.getOrDefault("name", "Unbekannte Leistung");
                String netAmount = service.getOrDefault("netAmount", "0,00");

                String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
                html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
                html.append(
                        "<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:55%;'>")
                        .append(escapeHtml(name)).append("</td>");
                html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;width:20%;'>")
                        .append(escapeHtml(vatRateLabel)).append("</td>");
                // Append € only for plain numeric amounts; values like "15 %"
                // (revenue participation from the price table) are shown as-is
                String amountDisplay = netAmount.matches("[0-9.,]+") ? netAmount + " €" : escapeHtml(netAmount);
                html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;width:25%;'>")
                        .append(amountDisplay).append("</td>");
                html.append("</tr>");
            }
        }

        html.append("</table>");

        // Summary section with actual totals from variables
        html.append("<div style='margin-top:8px;width:100%;'>");
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");

        // Nettosumme
        html.append("<tr>");
        html.append("<td style='width:55%;padding:2px 0;'></td>");
        html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
        html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
                .append(netTotal).append("</td>");
        html.append("</tr>");

        // Umsatzsteuer
        html.append("<tr>");
        html.append("<td style='width:55%;padding:2px 0;'></td>");
        html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. ")
                .append(escapeHtml(vatRateLabel)).append(" USt:</td>");
        html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
                .append(vatTotal).append("</td>");
        html.append("</tr>");

        // Gesamtsumme
        html.append("<tr>");
        html.append("<td style='width:55%;padding:2px 0;'></td>");
        html.append(
                "<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
        html.append(
                "<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
                .append(grossTotal).append("</td>");
        html.append("</tr>");

        html.append("</table>");
        html.append("</div>");
        html.append("</div>");

        return html.toString();
    }

    /**
     * Escape HTML special characters to prevent XSS.
     */
    private String escapeHtml(String input) {
        if (input == null) {
            return "";
        }
        return input.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
                .replace("'", "&#x27;");
    }

    /**
     * Erzeugt ein einfaches PDF für einen Stornobeleg gemäß R-19.
     * Verweist eindeutig auf die Originalrechnung (Nummer + Datum) und stellt die
     * Beträge als negative Werte dar.
     */
    public byte[] generateCancellationPdf(de.assecutor.votianlt.model.invoices.CustomerInvoice original,
            String cancellationNumber, java.time.LocalDate cancellationDate, String reason) throws Exception {
        return generateCorrectionDocumentPdf("STORNORECHNUNG", original, cancellationNumber, cancellationDate, reason,
                null, true);
    }

    /**
     * Erzeugt ein einfaches PDF für einen Berichtigungsbeleg gemäß R-13/R-14.
     * Verweist eindeutig auf die Originalrechnung und beschreibt die berichtigten Angaben.
     */
    public byte[] generateCorrectionPdf(de.assecutor.votianlt.model.invoices.CustomerInvoice original,
            String correctionNumber, java.time.LocalDate correctionDate, String reason, String correctedFields)
            throws Exception {
        return generateCorrectionDocumentPdf("RECHNUNGSBERICHTIGUNG", original, correctionNumber, correctionDate,
                reason, correctedFields, false);
    }

    private byte[] generateCorrectionDocumentPdf(String documentLabel,
            de.assecutor.votianlt.model.invoices.CustomerInvoice original, String number,
            java.time.LocalDate documentDate, String reason, String correctedFields, boolean negateAmounts)
            throws Exception {
        java.time.LocalDate effectiveDate = documentDate != null ? documentDate : java.time.LocalDate.now();
        java.time.format.DateTimeFormatter dateFmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy",
                Locale.GERMANY);

        BigDecimal net = negateAmounts ? negateOrZero(original.getNetAmount()) : safeAmount(original.getNetAmount());
        BigDecimal vat = negateAmounts ? negateOrZero(original.getVatAmount()) : safeAmount(original.getVatAmount());
        BigDecimal total = negateAmounts ? negateOrZero(original.getTotalAmount())
                : safeAmount(original.getTotalAmount());

        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html><html><head><meta charset='UTF-8'><style>");
        html.append("@page { size: A4; margin: 20mm 18mm 20mm 18mm; }");
        html.append("body { font-family: Arial, sans-serif; font-size: 11pt; color: #222; }");
        html.append("h1 { font-size: 18pt; letter-spacing: 0.05em; margin: 0 0 8pt 0; }");
        html.append("h2 { font-size: 13pt; margin: 18pt 0 6pt 0; }");
        html.append(".doc-number { font-size: 11pt; color: #555; }");
        html.append(".section { margin-top: 14pt; }");
        html.append(".reference { background: #f6f6f6; border-left: 3px solid #888; padding: 8pt 12pt; }");
        html.append(".reason { background: #fff8e6; border-left: 3px solid #d9a300; padding: 8pt 12pt; }");
        html.append("table.amounts { width: 60%; margin-left: 40%; border-collapse: collapse; margin-top: 10pt; }");
        html.append("table.amounts td { padding: 3pt 6pt; }");
        html.append("table.amounts td.value { text-align: right; }");
        html.append("table.amounts tr.total td { font-weight: bold; border-top: 1px solid #333; }");
        html.append(".addresses { width: 100%; margin-top: 14pt; }");
        html.append(".addresses td { width: 50%; vertical-align: top; }");
        html.append(".muted { color: #666; font-size: 9pt; }");
        html.append("</style></head><body>");

        html.append("<h1>").append(escapeHtml(documentLabel)).append("</h1>");
        html.append("<div class='doc-number'>Beleg-Nr.: ")
                .append(escapeHtml(safe(number)))
                .append(" &nbsp;·&nbsp; Datum: ").append(escapeHtml(effectiveDate.format(dateFmt)))
                .append("</div>");

        // Sender / Empfänger
        html.append("<table class='addresses'><tr><td>");
        html.append("<strong>Aussteller</strong><br/>");
        html.append(formatAddressBlock(original.getSenderName(), original.getSenderAddress(),
                original.getSenderPostcode(), original.getSenderCity(), original.getSenderCountry()));
        if (original.getSenderTaxNumber() != null && !original.getSenderTaxNumber().isBlank()) {
            html.append("<div class='muted'>Steuernr.: ").append(escapeHtml(original.getSenderTaxNumber()))
                    .append("</div>");
        }
        if (original.getSenderVatId() != null && !original.getSenderVatId().isBlank()) {
            html.append("<div class='muted'>USt-IdNr.: ").append(escapeHtml(original.getSenderVatId()))
                    .append("</div>");
        }
        html.append("</td><td>");
        html.append("<strong>Empfänger</strong><br/>");
        html.append(formatAddressBlock(
                firstNonBlank(original.getRecipientCompany(), original.getRecipientName()),
                original.getRecipientAddress(), original.getRecipientPostcode(), original.getRecipientCity(),
                original.getRecipientCountry()));
        html.append("</td></tr></table>");

        // Eindeutige Referenz auf Originalrechnung (R-13/R-19/R-28)
        html.append("<div class='section reference'>");
        html.append("<strong>Bezug:</strong> ");
        html.append("Diese ").append(escapeHtml(documentLabel.toLowerCase(Locale.GERMANY))).append(" bezieht sich ");
        html.append("eindeutig auf die Rechnung <strong>")
                .append(escapeHtml(safe(original.getInvoiceNumber()))).append("</strong>");
        if (original.getInvoiceDate() != null) {
            html.append(" vom ").append(escapeHtml(original.getInvoiceDate().format(dateFmt)));
        }
        html.append(".");
        html.append("</div>");

        if (correctedFields != null && !correctedFields.isBlank()) {
            html.append("<div class='section'><h2>Berichtigte Angaben</h2>");
            html.append("<div>").append(escapeHtml(correctedFields).replace("\n", "<br/>")).append("</div></div>");
        }

        if (reason != null && !reason.isBlank()) {
            html.append("<div class='section reason'><strong>Grund:</strong> ")
                    .append(escapeHtml(reason).replace("\n", "<br/>")).append("</div>");
        }

        html.append("<h2>Beträge</h2>");
        html.append("<table class='amounts'>");
        html.append("<tr><td>Nettobetrag</td><td class='value'>").append(formatCurrency(net)).append("</td></tr>");
        if (original.getVatRate() != null) {
            BigDecimal vatPct = original.getVatRate().multiply(new BigDecimal("100"))
                    .setScale(2, java.math.RoundingMode.HALF_UP).stripTrailingZeros();
            if (vatPct.scale() < 0) {
                vatPct = vatPct.setScale(0);
            }
            html.append("<tr><td>zzgl. ").append(vatPct.toPlainString().replace('.', ','))
                    .append("% USt</td><td class='value'>").append(formatCurrency(vat)).append("</td></tr>");
        } else {
            html.append("<tr><td>zzgl. USt</td><td class='value'>").append(formatCurrency(vat)).append("</td></tr>");
        }
        html.append("<tr class='total'><td>Gesamtbetrag</td><td class='value'>").append(formatCurrency(total))
                .append("</td></tr>");
        html.append("</table>");

        html.append("<div class='section muted'>");
        html.append("Hinweis: Dieser Beleg ersetzt die Originalrechnung nicht. Original und ");
        html.append(escapeHtml(documentLabel.toLowerCase(Locale.GERMANY)));
        html.append(" sind gemeinsam aufzubewahren.");
        html.append("</div>");

        html.append("</body></html>");

        return generatePdfFromHtmlString(html.toString());
    }

    private BigDecimal safeAmount(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }

    private BigDecimal negateOrZero(BigDecimal value) {
        return value != null ? value.negate() : BigDecimal.ZERO;
    }

    private String formatAddressBlock(String name, String street, String postcode, String city, String country) {
        StringBuilder sb = new StringBuilder();
        if (name != null && !name.isBlank()) {
            sb.append(escapeHtml(name)).append("<br/>");
        }
        if (street != null && !street.isBlank()) {
            sb.append(escapeHtml(street)).append("<br/>");
        }
        String line = String.join(" ", filterBlanks(postcode, city)).trim();
        if (!line.isEmpty()) {
            sb.append(escapeHtml(line)).append("<br/>");
        }
        if (country != null && !country.isBlank()) {
            sb.append(escapeHtml(country));
        }
        return sb.toString();
    }

    private java.util.List<String> filterBlanks(String... values) {
        java.util.List<String> out = new java.util.ArrayList<>();
        for (String v : values) {
            if (v != null && !v.isBlank()) {
                out.add(v);
            }
        }
        return out;
    }

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