package de.assecutor.pdftool.template;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itextpdf.html2pdf.HtmlConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Rendert ein Canvas-Template des Rechnungsgenerators nach PDF. Das Template
 * ist ein JSON der Form {@code {"elements": [...]}} mit prozentualen
 * Koordinaten; daraus wird absolut positioniertes HTML (mm auf A4) erzeugt und
 * mit iText html2pdf konvertiert. Portiert aus votianlt
 * (CustomerInvoiceService.generatePdfFromCanvasTemplate).
 */
@Service
public class TemplatePdfService {

    private static final Logger log = LoggerFactory.getLogger(TemplatePdfService.class);

    /**
     * Rendert das Template mit den übergebenen Variablenwerten (z. B.
     * {@code masterdata.*}, {@code customer.*}, {@code services.json}).
     */
    public byte[] generatePdf(String jsonTemplateData, Map<String, String> variables, BigDecimal vatRate) {
        try {
            return renderPdf(jsonTemplateData, variables, vatRate);
        } catch (Exception e) {
            throw new TemplateRenderException(
                    "Das Rechnungstemplate konnte nicht als PDF gerendert werden: " + e.getMessage(), e);
        }
    }

    private byte[] renderPdf(String jsonTemplateData, Map<String, String> variables, BigDecimal vatRate)
            throws Exception {
        BigDecimal effectiveVatRate = vatRate != null ? vatRate : new BigDecimal("0.19");
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readTree(jsonTemplateData);
        JsonNode elements = rootNode.get("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 (JsonNode element : elements) {
                appendElement(htmlBuilder, element, variables, effectiveVatRate);
            }
        }

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

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        HtmlConverter.convertToPdf(htmlBuilder.toString(), out);
        return out.toByteArray();
    }

    private void appendElement(StringBuilder htmlBuilder, JsonNode element, Map<String, String> variables,
            BigDecimal effectiveVatRate) {
        String type = element.has("type") ? element.get("type").asText("text") : "text";
        String variable = element.has("variable") ? element.get("variable").asText(null) : null;
        // Elemente mit Variable beziehen ihren Text zur Renderzeit aus der
        // Variablen-Map; nur freie Elemente verwenden den gespeicherten Text.
        String text;
        if (variable != null && !variable.isEmpty()) {
            text = "";
        } else {
            text = element.has("text") ? element.get("text").asText("") : "";
        }

        // Prozent-Koordinaten; Legacy-Fallback: Pixel auf Basis 595x842
        double xPercent = element.has("xPercent") ? element.get("xPercent").asDouble(0)
                : element.get("x").asDouble(0) / 595.0 * 100;
        double yPercent = element.has("yPercent") ? element.get("yPercent").asDouble(0)
                : element.get("y").asDouble(0) / 842.0 * 100;
        double widthPercent = element.has("widthPercent") ? element.get("widthPercent").asDouble(15)
                : element.get("width").asDouble(150) / 595.0 * 100;
        double heightPercent = element.has("heightPercent") ? element.get("heightPercent").asDouble(3)
                : element.get("height").asDouble(30) / 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";

        // Prozent -> mm (A4: 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(Locale.US, "%.2f", mmX)).append("mm;");
        htmlBuilder.append("top:").append(String.format(Locale.US, "%.2f", mmY)).append("mm;");
        if ("line".equals(type)) {
            htmlBuilder.append("width:").append(String.format(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(Locale.US, "%.2f", mmHeight)).append("mm;");
        } else {
            htmlBuilder.append("width:").append(String.format(Locale.US, "%.2f", mmWidth)).append("mm;");
            htmlBuilder.append("height:").append(String.format(Locale.US, "%.2f", mmHeight)).append("mm;");
            htmlBuilder.append("font-size:").append(fontSize).append("pt;");
            htmlBuilder.append("line-height:").append(String.format(Locale.US, "%.2f", fontSize * 1.2)).append("pt;");
            htmlBuilder.append("color:").append(color).append(";");
            // services.list als Block, damit die Tabelle die Breite füllen kann
            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("'>");

        // Variablen zuerst ersetzen, dann escapen
        if (variable != null && variables.containsKey(variable)) {
            text = variables.get(variable);
        } else if (text != null && text.startsWith("{{")) {
            // Legacy-Format {{variable}}|Anzeigetext bzw. {{variable}}
            int pipeIndex = text.indexOf('|');
            String placeholderPart = pipeIndex > -1 ? text.substring(0, pipeIndex) : text;
            if (placeholderPart.startsWith("{{") && placeholderPart.endsWith("}}")) {
                String varName = placeholderPart.substring(2, placeholderPart.length() - 2);
                if (variables.containsKey(varName)) {
                    text = variables.get(varName);
                }
            }
        }

        if (text != null) {
            text = escapeHtml(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)) {
            if (variables.containsKey("services.json")) {
                htmlBuilder.append(generateServicesTableHtmlWithData(variables));
            } else {
                htmlBuilder.append(generateServicesTableHtml(effectiveVatRate));
            }
        } else if (text.contains("<br>")) {
            // Mehrzeiliger Text: ohne nowrap rendern, damit <br> wirkt
            htmlBuilder.append("<span>").append(text).append("</span>");
        } else {
            htmlBuilder.append("<span style='white-space:nowrap;'>").append(text).append("</span>");
        }

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

    /** Positionstabelle mit Beispieldaten, wenn keine echten Positionen übergeben wurden. */
    private String generateServicesTableHtml(BigDecimal vatRate) {
        BigDecimal pct = vatRate.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP)
                .stripTrailingZeros();
        if (pct.scale() < 0) {
            pct = pct.setScale(0);
        }
        String vatLabel = pct.toPlainString().replace('.', ',') + "%";

        String[][] sampleData = { { "Beratungsleistung", vatLabel, "450,00 €" },
                { "Softwareentwicklung", vatLabel, "1.200,00 €" }, { "Support-Pauschale", vatLabel, "150,00 €" } };

        double netTotal = 1800.00;
        double grossTotal = netTotal + (netTotal * vatRate.doubleValue());

        StringBuilder html = new StringBuilder();
        html.append("<div style='width:100%;box-sizing:border-box;'>");
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
        html.append(tableHeaderRow());
        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>");

        html.append("<div style='margin-top:8px;width:100%;'>");
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
        html.append(summaryRow("Nettosumme:", String.format(Locale.GERMANY, "%,.2f €", netTotal), false));
        html.append(summaryRow("Gesamtsumme:", String.format(Locale.GERMANY, "%,.2f €", grossTotal), true));
        html.append("</table>");
        html.append("</div>");
        html.append("</div>");
        return html.toString();
    }

    /** Positionstabelle aus echten Daten (services.json + invoice.*-Summen). */
    private String generateServicesTableHtmlWithData(Map<String, String> 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%");

        List<Map<String, String>> servicesData = new ArrayList<>();
        String servicesJson = variables.get("services.json");
        if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
            try {
                servicesData = new ObjectMapper().readValue(servicesJson, new TypeReference<>() {
                });
            } catch (Exception e) {
                log.warn("Positionsdaten (services.json) konnten nicht gelesen werden: {}", e.getMessage());
            }
        }

        StringBuilder html = new StringBuilder();
        html.append("<div style='width:100%;box-sizing:border-box;'>");
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
        html.append(tableHeaderRow());

        if (servicesData.isEmpty()) {
            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 Positionen vorhanden</td>");
            html.append("</tr>");
        } else {
            for (int i = 0; i < servicesData.size(); i++) {
                Map<String, String> service = servicesData.get(i);
                String name = service.getOrDefault("name", "Unbekannte Position");
                String netAmount = service.getOrDefault("netAmount", "0,00");
                // USt-Satz pro Position, Fallback: einheitlicher Satz der Rechnung
                String rowVat = service.getOrDefault("vat", vatRateLabel);

                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(rowVat)).append("</td>");
                // € nur an rein numerische Beträge anhängen; Werte wie "15 %" unverändert
                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>");

        html.append("<div style='margin-top:8px;width:100%;'>");
        html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
        // Bei gemischten USt-Sätzen (leeres Label) ohne Satzangabe beschriften
        String vatSummaryLabel = vatRateLabel.isEmpty() ? "zzgl. USt:"
                : "zzgl. " + escapeHtml(vatRateLabel) + " USt:";
        html.append(summaryRow("Nettosumme:", netTotal, false));
        html.append(summaryRow(vatSummaryLabel, vatTotal, false));
        html.append(summaryRow("Gesamtsumme:", grossTotal, true));
        html.append("</table>");
        html.append("</div>");
        html.append("</div>");
        return html.toString();
    }

    private String tableHeaderRow() {
        return "<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>"
                + "<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>"
                + "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>"
                + "<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>"
                + "</tr>";
    }

    private String summaryRow(String label, String value, boolean emphasized) {
        String labelStyle = emphasized ? "padding:4px 8px;font-weight:bold;font-size:1.05em;" : "padding:2px 8px;";
        String valueStyle = emphasized ? "padding:4px 8px;font-weight:bold;font-size:1.05em;"
                : "padding:2px 8px;font-weight:bold;";
        return "<tr>"
                + "<td style='width:55%;padding:2px 0;'></td>"
                + "<td style='width:20%;text-align:left;white-space:nowrap;" + labelStyle + "'>" + label + "</td>"
                + "<td style='width:25%;text-align:right;white-space:nowrap;" + valueStyle + "'>" + value + "</td>"
                + "</tr>";
    }

    private String escapeHtml(String input) {
        if (input == null) {
            return "";
        }
        return input.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
                .replace("'", "&#x27;");
    }
}
