import { FastifyInstance } from "fastify";
import QRCode from "qrcode";
import prisma from "../../config/database";
import { requirePermission } from "../../middleware/auth";
import { nasInvoicesManager } from "../../services/nas-financials-manager";
import { htmlToPdf } from "../../utils/html-to-pdf";
import { getRate } from "../../services/exchange-rates";
import { localDateStr } from "../../utils/date";
import { parseId, success } from "../../utils/response";
import {
  formatDate,
  formatNum,
  escapeHtml,
  cleanQuillHtml,
  buildQuillIndentCss,
  logoDataUriFromSettings,
  buildPdfHeaderTemplate,
} from "../../utils/pdf-shared";
import createDOMPurify from "dompurify";
import { JSDOM } from "jsdom";

const window = new JSDOM("").window;
const DOMPurify = createDOMPurify(window);

/* ── Helpers ─────────────────────────────────────────────────────── */

interface AddressResult {
  name: string;
  lines: string[];
}

function buildAddressLines(
  entity: Record<string, unknown> | null,
  isSupplier: boolean,
  tObj: Record<string, string>,
): AddressResult {
  if (!entity) return { name: "", lines: [] };

  const nameKey = isSupplier ? "company_name" : "name";
  const name = String(entity[nameKey] || "");

  let cfData: Array<{ name?: string; value?: string; showLabel?: boolean }> =
    [];
  let fieldOrder: string[] | null = null;
  const raw = entity.custom_fields;
  if (raw) {
    let parsed: unknown;
    try {
      parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
    } catch {
      parsed = null;
    }
    if (parsed && typeof parsed === "object") {
      if ((parsed as Record<string, unknown>).fields) {
        cfData =
          ((parsed as Record<string, unknown>).fields as typeof cfData) || [];
        fieldOrder = ((parsed as Record<string, unknown>).field_order ||
          (parsed as Record<string, unknown>).fieldOrder) as string[] | null;
      } else if (Array.isArray(parsed)) {
        cfData = parsed;
      }
    }
  }

  if (Array.isArray(fieldOrder)) {
    const legacyMap: Record<string, string> = {
      Name: "name",
      CompanyName: "company_name",
      Street: "street",
      CityPostal: "city_postal",
      Country: "country",
      CompanyId: "company_id",
      VatId: "vat_id",
    };
    fieldOrder = fieldOrder.map((k) => legacyMap[k] || k);
  }

  const fieldMap: Record<string, string> = {};
  if (name) fieldMap[nameKey] = name;
  if (entity.street) fieldMap.street = String(entity.street);
  const cityParts = [entity.city || "", entity.postal_code || ""]
    .filter(Boolean)
    .map(String);
  const cityPostal = cityParts.join(" ").trim();
  if (cityPostal) fieldMap.city_postal = cityPostal;
  if (entity.country) fieldMap.country = String(entity.country);
  if (entity.company_id)
    fieldMap.company_id = `${tObj.ico}${entity.company_id}`;
  if (entity.vat_id) fieldMap.vat_id = `${tObj.dic}${entity.vat_id}`;

  cfData.forEach((cf, i) => {
    const cfName = (cf.name || "").trim();
    const cfValue = (cf.value || "").trim();
    const showLabel = cf.showLabel !== false;
    if (cfValue) {
      fieldMap[`custom_${i}`] =
        showLabel && cfName ? `${cfName}: ${cfValue}` : cfValue;
    }
  });

  const lines: string[] = [];
  if (Array.isArray(fieldOrder) && fieldOrder.length) {
    for (const key of fieldOrder) {
      if (key === nameKey) continue;
      if (fieldMap[key]) lines.push(fieldMap[key]);
    }
    for (const [key, line] of Object.entries(fieldMap)) {
      if (key === nameKey) continue;
      if (!fieldOrder!.includes(key)) lines.push(line);
    }
  } else {
    for (const [key, line] of Object.entries(fieldMap)) {
      if (key === nameKey) continue;
      lines.push(line);
    }
  }

  return { name, lines };
}

/* ── Translations ────────────────────────────────────────────────── */

const paymentMethodMap: Record<string, Record<string, string>> = {
  cs: {
    "Bank transfer": "Příkazem",
    Cash: "Hotově",
    "Cash on delivery": "Dobírka",
  },
  en: {
    Příkazem: "Bank transfer",
    Hotově: "Cash",
    Dobírka: "Cash on delivery",
  },
};

function translatePaymentMethod(value: string | null, lang: string): string {
  if (!value) return "";
  return paymentMethodMap[lang]?.[value] || value;
}

const translations: Record<string, Record<string, string>> = {
  cs: {
    title: "Faktura",
    heading: "FAKTURA - DAŇOVÝ DOKLAD č.",
    supplier: "Dodavatel",
    customer: "Odběratel",
    bank: "Banka:",
    swift: "SWIFT:",
    iban: "IBAN:",
    account_no: "Číslo účtu:",
    var_symbol: "Variabilní s.:",
    const_symbol: "Konstantní s.:",
    order_no: "Objednávka č.:",
    order_date: "Objednávka ze dne:",
    issue_date: "Datum vystavení:",
    due_date: "Datum splatnosti:",
    tax_date: "Datum uskutečnění plnění:",
    payment_method: "Forma úhrady:",
    billing: "Fakturujeme Vám za:",
    col_no: "Č.",
    col_desc: "Popis",
    col_qty: "Množství",
    col_unit_price: "Jedn. cena",
    col_price: "Cena",
    col_vat_pct: "%DPH",
    col_vat: "DPH",
    col_total: "Celkem",
    subtotal: "Mezisoučet:",
    vat_label: "DPH",
    total: "Celkem k úhradě",
    amounts_in: "Částky jsou uvedeny v",
    notes: "Poznámky",
    issued_by: "Vystavil:",
    notice:
      "Dovolujeme si Vás upozornit, že v případě nedodržení data splatnosti" +
      " uvedeného na faktuře Vám budeme účtovat úrok z prodlení v dohodnuté, resp." +
      " zákonné výši a smluvní pokutu (byla-li sjednána).",
    vat_recap: "Rekapitulace DPH v Kč:",
    vat_base: "Základ v Kč",
    vat_rate: "Sazba",
    vat_amount: "DPH v Kč",
    vat_with_total: "Celkem s DPH v Kč",
    received_by: "Převzal:",
    stamp: "Razítko:",
    ico: "IČ: ",
    dic: "DIČ: ",
    cnb_rate: "Přepočet kurzem ČNB ke dni",
  },
  en: {
    title: "Invoice",
    heading: "INVOICE - TAX DOCUMENT No.",
    supplier: "Supplier",
    customer: "Customer",
    bank: "Bank:",
    swift: "SWIFT:",
    iban: "IBAN:",
    account_no: "Account No.:",
    var_symbol: "Variable symbol:",
    const_symbol: "Constant symbol:",
    order_no: "Order No.:",
    order_date: "Order date:",
    issue_date: "Issue date:",
    due_date: "Due date:",
    tax_date: "Tax point date:",
    payment_method: "Payment method:",
    billing: "We invoice you for:",
    col_no: "No.",
    col_desc: "Description",
    col_qty: "Quantity",
    col_unit_price: "Unit price",
    col_price: "Price",
    col_vat_pct: "VAT%",
    col_vat: "VAT",
    col_total: "Total",
    subtotal: "Subtotal:",
    vat_label: "VAT",
    total: "Total to pay",
    amounts_in: "Amounts are in",
    notes: "Notes",
    issued_by: "Issued by:",
    notice:
      "Please note that in case of late payment, we will charge default interest" +
      " at the agreed or statutory rate and a contractual penalty (if agreed).",
    vat_recap: "VAT recapitulation in CZK:",
    vat_base: "Tax base in CZK",
    vat_rate: "Rate",
    vat_amount: "VAT in CZK",
    vat_with_total: "Total incl. VAT in CZK",
    received_by: "Received by:",
    stamp: "Stamp:",
    ico: "Reg. No.: ",
    dic: "Tax ID: ",
    cnb_rate: "CNB exchange rate as of",
  },
};

/** Tag-stripped emptiness check — "<p><br></p>" (empty Quill) counts as empty. */
function stripTags(html: string | null | undefined): string {
  return (html || "").replace(/<[^>]*>/g, "").trim();
}

/**
 * Repeating per-page footer for the invoice PDF, rendered by Puppeteer in the
 * bottom page margin (mirrors buildIssuedOrderPdfFooter): "Vystavil: <name>"
 * left, "Strana X z Y" / "Page X of Y" (by document language) centered.
 * Styles must stay inline and the font-size explicit — Chromium defaults
 * footer text to 0.
 */
export function buildInvoicePdfFooter(
  lang: string,
  issuerLine: string,
): string {
  const t = translations[lang] || translations.cs;
  const pageWord = lang === "cs" ? "Strana" : "Page";
  const ofWord = lang === "cs" ? "z" : "of";
  return `<div style="width:100%; font-size:8pt; font-family:Tahoma, sans-serif; color:#646464; padding:0 12mm; box-sizing:border-box; display:flex; align-items:center;">
    <div style="flex:1; text-align:left;"><span style="font-weight:600;">${escapeHtml(t.issued_by)}</span> ${escapeHtml(issuerLine)}</div>
    <div style="flex:1; text-align:center;">${pageWord} <span class="pageNumber"></span> ${ofWord} <span class="totalPages"></span></div>
    <div style="flex:1;"></div>
  </div>`;
}

/**
 * Repeating per-page header for the invoice PDF — the unified red-accent
 * family header (shared with issued orders): logo left, red
 * "FAKTURA - DAŇOVÝ DOKLAD č. X" right, red rule underneath, on EVERY page.
 * The body's own header is print-hidden so page 1 doesn't show it twice.
 */
export function buildInvoicePdfHeader(
  lang: string,
  logoDataUri: string,
  invoiceNumber: string,
): string {
  const t = translations[lang] || translations.cs;
  return buildPdfHeaderTemplate(logoDataUri, `${t.heading} ${invoiceNumber}`);
}

/* ── Route ───────────────────────────────────────────────────────── */

export default async function invoicesPdfRoutes(
  fastify: FastifyInstance,
): Promise<void> {
  fastify.get<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requirePermission("invoices.view") },
    async (request, reply) => {
      const id = parseId(request.params.id, reply);
      if (id === null) return;
      const query = request.query as Record<string, string>;

      try {
        const lang = query.lang === "en" ? "en" : "cs";
        const t = translations[lang];

        const invoice = await prisma.invoices.findUnique({
          where: { id },
        });

        if (!invoice) {
          return reply
            .status(404)
            .type("text/html")
            .send("<html><body><h1>Faktura nenalezena</h1></body></html>");
        }

        const items = await prisma.invoice_items.findMany({
          where: { invoice_id: id },
          orderBy: { position: "asc" },
        });
        // id tiebreak: position can repeat, keep the order deterministic.
        const sections = await prisma.invoice_sections.findMany({
          where: { invoice_id: id },
          orderBy: [{ position: "asc" }, { id: "asc" }],
        });

        let customer: Record<string, unknown> | null = null;
        if (invoice.customer_id) {
          customer = (await prisma.customers.findUnique({
            where: { id: invoice.customer_id },
          })) as Record<string, unknown> | null;
        }

        const settings = (await prisma.company_settings.findFirst()) as Record<
          string,
          unknown
        > | null;

        let orderNumber = "";
        let orderDate = "";
        if (invoice.order_id) {
          const orderRow = await prisma.orders.findUnique({
            where: { id: invoice.order_id },
            select: {
              order_number: true,
              customer_order_number: true,
              created_at: true,
            },
          });
          if (orderRow) {
            orderNumber = escapeHtml(
              String(
                orderRow.customer_order_number || orderRow.order_number || "",
              ),
            );
            if (orderRow.created_at) {
              orderDate = formatDate(orderRow.created_at);
            }
          }
        }

        // Logo as a data URI — used by the body header (screen/HTML view) AND
        // the Puppeteer headerTemplate (templates can only load data: URLs).
        const logoDataUri = logoDataUriFromSettings(settings);
        const logoImg = logoDataUri
          ? `<img src="${logoDataUri}" class="logo" />`
          : "";

        const currency = invoice.currency || "CZK";
        const applyVat = !!invoice.apply_vat;

        const vatSummary: Record<string, { base: number; vat: number }> = {};
        let subtotal = 0;

        for (const item of items) {
          const lineSubtotal = Number(item.quantity) * Number(item.unit_price);
          subtotal += lineSubtotal;
          const rate = Number(item.vat_rate);
          const key = String(rate);
          if (!vatSummary[key]) vatSummary[key] = { base: 0, vat: 0 };
          vatSummary[key].base += lineSubtotal;
          if (applyVat) {
            vatSummary[key].vat +=
              Math.round(((lineSubtotal * rate) / 100) * 100) / 100;
          }
        }

        let totalVat = 0;
        for (const data of Object.values(vatSummary)) {
          totalVat += data.vat;
        }
        const totalToPay = subtotal + totalVat;

        // QR code - SPAYD payment format
        let qrSvg = "";
        try {
          const spaydParts = [
            "SPD*1.0",
            "ACC:" + (invoice.bank_iban || "").replace(/ /g, ""),
            "AM:" + totalToPay.toFixed(2),
            "CC:" + currency,
            "X-VS:" + (invoice.invoice_number || ""),
            "X-KS:" + (invoice.constant_symbol || "0308"),
            "MSG:" + t.title + " " + (invoice.invoice_number || ""),
          ];
          const spaydString = spaydParts.join("*");
          qrSvg = await QRCode.toString(spaydString, {
            type: "svg",
            errorCorrectionLevel: "M",
            margin: 1,
            width: 200,
          });
        } catch {
          // QR generation failed — leave empty
        }

        // VAT recapitulation (always in CZK — Czech tax requirement)
        const isForeign = currency.toUpperCase() !== "CZK";
        const issueDateStr = invoice.issue_date
          ? localDateStr(new Date(invoice.issue_date))
          : undefined;
        const cnbRate = isForeign ? await getRate(currency, issueDateStr) : 1.0;
        const vatRates = [21, 12, 0];
        const vatRecap: Array<{
          rate: number;
          base: number;
          vat: number;
          total: number;
        }> = [];
        for (const rate of vatRates) {
          const key = String(rate);
          const base = vatSummary[key]?.base ?? 0;
          const vat = vatSummary[key]?.vat ?? 0;
          vatRecap.push({
            rate,
            base: Math.round(base * cnbRate * 100) / 100,
            vat: Math.round(vat * cnbRate * 100) / 100,
            total: Math.round((base + vat) * cnbRate * 100) / 100,
          });
        }

        const supp = buildAddressLines(settings, true, t);
        const cust = buildAddressLines(customer, false, t);

        const suppLinesHtml = supp.lines
          .map((l) => `<div class="address-line">${escapeHtml(l)}</div>`)
          .join("");
        const custLinesHtml = cust.lines
          .map((l) => `<div class="address-line">${escapeHtml(l)}</div>`)
          .join("");

        // Supplier email/web from custom_fields
        let suppEmail = "";
        if (settings?.custom_fields) {
          const raw = settings.custom_fields;
          // Malformed JSON in custom_fields must degrade gracefully (no email)
          // instead of 500-ing the whole invoice PDF.
          let parsed: unknown;
          try {
            parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
          } catch (e) {
            request.log.warn(
              e,
              "Neplatný JSON v company_settings.custom_fields — e-mail dodavatele vynechán",
            );
            parsed = null;
          }
          if (parsed && typeof parsed === "object") {
            const fields = (parsed as Record<string, unknown>).fields;
            if (Array.isArray(fields)) {
              for (const f of fields) {
                if (f.name && f.name.toLowerCase() === "email" && f.value) {
                  suppEmail = String(f.value);
                }
              }
            }
          }
        }

        const invoiceNumber = escapeHtml(invoice.invoice_number);

        const itemsHtml = items
          .map((item, i) => {
            const qty = Number(item.quantity);
            const unitPrice = Number(item.unit_price);
            const lineSubtotal = qty * unitPrice;
            const vatRate = Number(item.vat_rate);
            const lineVat = applyVat ? (lineSubtotal * vatRate) / 100 : 0;
            const lineTotal = lineSubtotal + lineVat;
            const qtyDecimals = Math.floor(qty) === qty ? 0 : 2;
            const subDesc = item.item_description || "";

            return `<tr>
            <td class="row-num">${i + 1}</td>
            <td class="desc">${escapeHtml(item.description)}${subDesc ? `<div class="item-subdesc">${escapeHtml(subDesc)}</div>` : ""}</td>
            <td class="center">${formatNum(qty, qtyDecimals)}${item.unit ? ` / ${escapeHtml(item.unit)}` : ""}</td>
            <td class="right">${formatNum(unitPrice)}</td>
            <td class="right">${formatNum(lineSubtotal)}</td>
            <td class="center">${applyVat ? Math.floor(vatRate) : 0}%</td>
            <td class="right">${formatNum(lineVat)}</td>
            <td class="right total-cell">${formatNum(lineTotal)}</td>
        </tr>`;
          })
          .join("");

        const vatRecapHtml = vatRecap
          .map(
            (vr) => `<tr>
            <td class="right">${formatNum(vr.base)}</td>
            <td class="center">${Math.floor(vr.rate)}%</td>
            <td class="right">${formatNum(vr.vat)}</td>
            <td class="right">${formatNum(vr.total)}</td>
        </tr>`,
          )
          .join("");

        let vatDetailHtml = "";
        if (applyVat) {
          for (const [rate, data] of Object.entries(vatSummary)) {
            if (data.vat > 0) {
              vatDetailHtml += `
        <div class="row">
          <span class="label">${escapeHtml(t.vat_label)} ${Math.floor(Number(rate))}%:</span>
          <span class="value">${formatNum(data.vat)} ${escapeHtml(currency)}</span>
        </div>`;
            }
          }
        }

        // ── Sections ("Obsah") — flow INLINE right after the items/totals
        // block (mirrors issued orders): no separate page, long content
        // paginates naturally and the Puppeteer footer template stamps every
        // page. Suppressed entirely when every section is empty (tag-stripped
        // check, so an empty-Quill "<p><br></p>" doesn't count). cs prefers
        // the Czech title when present; en (or a missing Czech title) falls
        // back to the EN title.
        const resolveSectionTitle = (s: {
          title: string | null;
          title_cz: string | null;
        }): string =>
          lang === "cs" && (s.title_cz || "").trim()
            ? s.title_cz || ""
            : s.title || "";
        // Visibility must use the SAME per-language title rule as the render
        // loop — otherwise a section with only the other language's title
        // would count as content and produce an empty sections block.
        const visibleSections = sections.filter(
          (s) => resolveSectionTitle(s).trim() || stripTags(s.content),
        );
        let sectionsHtml = "";
        if (visibleSections.length > 0) {
          sectionsHtml += '<div class="scope-sections">';
          for (const section of visibleSections) {
            const title = resolveSectionTitle(section);
            const content = (section.content || "").trim();
            sectionsHtml += '<div class="scope-section">';
            if (title.trim())
              sectionsHtml += `<div class="scope-section-title">${escapeHtml(title)}</div>`;
            if (content)
              sectionsHtml += `<div class="section-content">${cleanQuillHtml(DOMPurify.sanitize(content))}</div>`;
            sectionsHtml += "</div>";
          }
          sectionsHtml += "</div>";
        }

        // Quill indent CSS
        const indentCSS = buildQuillIndentCss();

        const html = `<!DOCTYPE html>
<html lang="${escapeHtml(lang)}">
<head>
<meta charset="utf-8" />
<title>${escapeHtml(t.title)} ${invoiceNumber}</title>
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<style>
  @page {
    size: A4;
    /* Chromium uses THESE margins for layout (they override Puppeteer's
       margin option) — they must match the header/footer template space:
       32mm top (22mm logo + red heading + rule), 18mm bottom (Vystavil +
       Strana X z Y). Same geometry as the issued-order PDF. */
    margin: 32mm 12mm 18mm 12mm;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    font-family: "Segoe UI", Tahoma, Arial, sans-serif;
    font-size: 10pt;
    color: #1a1a1a;
    width: 186mm;
  }

  .invoice-page {
    display: flex;
    flex-direction: column;
    /* Printable area with the Puppeteer header (32mm top) + footer (18mm
       bottom) margins, minus 1mm slack so a one-page invoice can't spill. */
    min-height: calc(297mm - 51mm);
  }
  .invoice-content { flex: 1 1 auto; }
  /* The bottom block (notice + QR/VAT recap + Převzal/Razítko) must never be
     SPLIT by a page break — on a one-page invoice the flex pinning keeps it
     at the page bottom; when the content overflows, break-inside moves the
     whole block to the next page as one unit instead of stranding the notice
     on the previous page. */
  .invoice-footer {
    flex-shrink: 0;
    break-inside: avoid;
  }

  .accent { color: #de3a3a; }

  /* ── Hlavicka ── */
  .invoice-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1mm;
    padding-bottom: 1mm;
    border-bottom: 2pt solid #de3a3a;
  }
  .invoice-header .left {
    display: flex;
    align-items: center;
    gap: 3mm;
  }
  .logo-header { text-align: left; }
  .company-title {
    font-size: 12pt;
    font-weight: 700;
  }
  .invoice-title {
    font-size: 13pt;
    font-weight: 700;
    color: #de3a3a;
    text-align: right;
    letter-spacing: 0.03em;
  }

  .logo {
    max-width: 42mm;
    max-height: 22mm;
    object-fit: contain;
  }

  /* ── Adresy ── */
  .header-grid {
    border: 0.5pt solid #d0d0d0;
    border-collapse: collapse;
    width: 100%;
    margin-bottom: 1mm;
  }
  .header-grid td {
    padding: 2mm 3mm;
    border: 0.5pt solid #d0d0d0;
    vertical-align: top;
    width: 50%;
  }
  .header-grid td.addr-customer {
    background: #f5f5f5;
  }
  .header-grid td.details-bank {
    background: #f5f5f5;
  }
  .address-label {
    font-size: 8pt;
    font-weight: 700;
    color: #de3a3a;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 1mm;
  }
  .address-name {
    font-size: 10pt;
    font-weight: 700;
    color: #1a1a1a;
    line-height: 1.3;
    margin-bottom: 1mm;
  }
  .address-line {
    font-size: 9pt;
    color: #444;
    line-height: 1.5;
  }

  /* ── Detaily (banka + datumy) — inside header-grid ── */

  .info-row {
    display: flex;
    align-items: baseline;
    font-size: 9pt;
    padding: 1mm 0;
    border-bottom: 0.5pt solid #f0f0f0;
  }
  .info-row:last-child { border-bottom: none; }
  .info-row .lbl {
    color: #666;
    font-weight: 400;
    flex-shrink: 0;
    white-space: nowrap;
    margin-right: 3mm;
  }
  .info-row .val {
    font-weight: 600;
    color: #1a1a1a;
    text-align: right;
    margin-left: auto;
  }

  /* VS/KS blok */
  .vs-block {
    font-size: 9pt;
    line-height: 1.4;
    padding-top: 2mm;
  }

  /* ── Polozky ── */
  .billing-label {
    font-weight: 700;
    color: #1a1a1a;
    font-size: 10pt;
    padding: 2mm 0 1mm 0;
    border-bottom: 1.5pt solid #de3a3a;
    margin-bottom: 0;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }

  table.items {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 9pt;
    margin-bottom: 2mm;
  }
  table.items thead th {
    font-size: 8.5pt;
    font-weight: 600;
    color: #646464;
    padding: 4px 4px;
    text-align: left;
    border-bottom: 0.5pt solid #d0d0d0;
    white-space: nowrap;
  }
  table.items thead th.center { text-align: center; }
  table.items thead th.right { text-align: right; }
  table.items tbody td {
    padding: 4px 4px;
    border-bottom: 0.5pt solid #e0e0e0;
    vertical-align: middle;
    color: #1a1a1a;
  }
  table.items tbody tr:nth-child(even) { background: #f8f9fa; }
  table.items tbody td.center { text-align: center; white-space: nowrap; }
  table.items tbody td.right { text-align: right; }
  table.items tbody td.row-num {
    text-align: center;
    color: #969696;
    font-size: 9pt;
  }
  table.items tbody td.desc {
    font-size: 9pt;
    font-weight: 600;
    color: #1a1a1a;
  }
  .item-subdesc {
    font-size: 9pt;
    color: #646464;
    margin-top: 2px;
  }
  table.items tbody td.total-cell { font-weight: 700; }

  /* Soucet + total - styl z nabidek */
  .totals-wrapper {
    display: flex;
    justify-content: flex-end;
    margin-top: 2mm;
  }
  .totals {
    width: 80mm;
  }
  .totals .detail-rows {
    margin-bottom: 3mm;
  }
  .totals .row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 9.5pt;
    color: #1a1a1a;
    margin-bottom: 2mm;
  }
  .totals .grand {
    border-top: 0.5pt solid #e0e0e0;
    padding-top: 4mm;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
  }
  .totals .grand .label {
    font-size: 10.5pt;
    font-weight: 400;
    color: #1a1a1a;
    align-self: center;
  }
  .totals .grand .value {
    font-size: 14pt;
    font-weight: 600;
    color: #1a1a1a;
    border-bottom: 2.5pt solid #de3a3a;
    padding-bottom: 1mm;
  }
  .totals .currency-note {
    text-align: right;
    font-size: 8pt;
    color: #1a1a1a;
    margin-top: 2mm;
  }

  /* Vystavil lives in Puppeteer's footerTemplate (bottom page margin) on
     every page — same as the issued-order PDF; no body block. */

  /* Upozorneni */
  .notice {
    font-size: 8pt;
    color: #1a1a1a;
    margin: 2mm 0;
    line-height: 1.3;
  }

  /* DPH rekapitulace + QR */
  .recap-section {
    display: flex;
    gap: 5mm;
    align-items: flex-start;
    margin-top: 1mm;
  }
  .recap-section .qr {
    flex-shrink: 0;
    width: 28mm;
  }
  .recap-section .qr img,
  .recap-section .qr svg { width: 28mm; height: 28mm; }

  .recap-section table {
    border-collapse: collapse;
    font-size: 9pt;
    flex: 1;
  }
  .recap-section table th {
    font-size: 8pt;
    font-weight: 600;
    color: #555;
    padding: 3px 6px;
    text-align: right;
    border-bottom: 0.5pt solid #ccc;
  }
  .recap-section table td {
    padding: 3px 6px;
    text-align: right;
    border-bottom: 0.5pt solid #eee;
  }
  .recap-section table td.center { text-align: center; }
  .recap-section table td.cnb-rate {
    font-size: 8pt;
    color: #888;
    text-align: right;
    border-bottom: none;
    padding-top: 4px;
  }

  /* Prevzal / razitko */
  .footer-row {
    display: flex;
    justify-content: space-between;
    margin-top: 4mm;
    font-size: 9pt;
    border-top: 0.5pt solid #aaa;
    padding-top: 2mm;
    min-height: 15mm;
  }
  .footer-row .col {
    font-weight: 600;
    color: #555;
  }

  /* Sekce ("Obsah") — tecou inline za tabulkou polozek; zadna nova stranka,
     dlouhy obsah strankuje prirozene a paticku tiskne Puppeteer na kazdou
     stranu (mirrors issued-orders-pdf). */
  .scope-sections {
    margin-top: 6mm;
  }
  .scope-section {
    width: 100%;
    max-width: 100%;
    margin-bottom: 3mm;
    break-inside: avoid;
  }
  .scope-section-title {
    font-size: 11pt;
    font-weight: 700;
    color: #1a1a1a;
    margin-bottom: 1mm;
  }
  .section-content {
    color: #1a1a1a;
    line-height: 1.5;
    word-break: normal;
    overflow-wrap: anywhere;
  }
  .section-content,
  .section-content * {
    font-family: Tahoma, sans-serif !important;
  }
  .section-content { font-size: 14px; }
  .section-content h1 { font-size: 20px; }
  .section-content h2 { font-size: 18px; }
  .section-content h3 { font-size: 16px; }
  .section-content h4 { font-size: 15px; }
  .section-content p { margin: 0 0 0.4em 0; }
  .section-content ul, .section-content ol { margin: 0 0 0.4em 1.5em; }
  .section-content li { margin-bottom: 0.2em; }

  /* Quill fonty – v PDF vynuceno Tahoma */
  [class*="ql-font-"] { font-family: Tahoma, sans-serif !important; }
  .ql-align-center  { text-align: center; }
  .ql-align-right   { text-align: right; }
  .ql-align-justify { text-align: justify; }
${indentCSS}

  @media print {
    body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    /* The logo + red heading print via Puppeteer's headerTemplate on EVERY
       page — hide the body copy so page 1 doesn't show it twice. The screen
       (HTML preview) keeps the body header. */
    .invoice-header { display: none; }
  }
  @media screen {
    html { background: #525659; }
    body {
      width: 100vw !important;
      margin: 0;
      padding: 30px 0;
      background: transparent;
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 100vh;
      overflow-x: hidden;
    }
    .invoice-page {
      width: 210mm;
      min-height: 297mm;
      padding: 15mm;
      background: white;
      box-shadow: 0 4px 20px rgba(0,0,0,0.3);
      box-sizing: border-box;
      border-radius: 2px;
    }
  }
</style>
</head>
<body>
<div class="invoice-page">
<div class="invoice-content">

  <!-- Hlavicka -->
  <div class="invoice-header">
    <div class="left">
      ${logoImg ? `<div class="logo-header">${logoImg}</div>` : ""}
    </div>
    <div class="invoice-title">${escapeHtml(t.heading)} ${invoiceNumber}</div>
  </div>

  <!-- Dodavatel / Odberatel + Banka / Datumy -->
  <table class="header-grid" cellspacing="0">
    <tr>
      <td>
        <div class="address-label">${escapeHtml(t.supplier)}</div>
        <div class="address-name">${escapeHtml(supp.name)}</div>
        ${suppLinesHtml}
      </td>
      <td class="addr-customer">
        <div class="address-label">${escapeHtml(t.customer)}</div>
        <div class="address-name">${escapeHtml(cust.name)}</div>
        ${custLinesHtml}
      </td>
    </tr>
    <tr>
      <td class="details-bank">
        <div class="info-row"><span class="lbl">${escapeHtml(t.bank)}</span> <span class="val">${escapeHtml(invoice.bank_name)}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.swift)}</span> <span class="val">${escapeHtml(invoice.bank_swift)}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.iban)}</span> <span class="val">${escapeHtml(invoice.bank_iban)}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.account_no)}</span> <span class="val">${escapeHtml(invoice.bank_account)}</span></div>
        <div class="vs-block">
          ${escapeHtml(t.var_symbol)} <strong>${invoiceNumber}</strong>
          &nbsp;&nbsp;&nbsp; ${escapeHtml(t.const_symbol)} <strong>${escapeHtml(invoice.constant_symbol)}</strong>
        </div>
      </td>
      <td>
        <div class="info-row"><span class="lbl">${escapeHtml(t.issue_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.issue_date))}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.due_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.due_date))}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.tax_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.tax_date))}</span></div>
        <div class="info-row"><span class="lbl">${escapeHtml(t.payment_method)}</span> <span class="val">${escapeHtml(translatePaymentMethod(invoice.payment_method, lang))}</span></div>
        ${orderNumber ? `<div class="info-row"><span class="lbl">${escapeHtml(t.order_no)}</span> <span class="val">${orderNumber}</span></div>` : ""}
        ${orderDate ? `<div class="info-row"><span class="lbl">${escapeHtml(t.order_date)}</span> <span class="val">${escapeHtml(orderDate)}</span></div>` : ""}
      </td>
    </tr>
  </table>

  <!-- Polozky -->
  <div class="billing-label">${escapeHtml(invoice.billing_text || t.billing)}</div>
  <table class="items">
    <thead>
      <tr>
        <th class="center" style="width:3%">${escapeHtml(t.col_no)}</th>
        <th style="width:36%">${escapeHtml(t.col_desc)}</th>
        <th class="center" style="width:10%">${escapeHtml(t.col_qty)}</th>
        <th class="right" style="width:10%">${escapeHtml(t.col_unit_price)}</th>
        <th class="right" style="width:10%">${escapeHtml(t.col_price)}</th>
        <th class="center" style="width:5%">${escapeHtml(t.col_vat_pct)}</th>
        <th class="right" style="width:10%">${escapeHtml(t.col_vat)}</th>
        <th class="right" style="width:16%">${escapeHtml(t.col_total)}</th>
      </tr>
    </thead>
    <tbody>
      ${itemsHtml}
    </tbody>
  </table>

  <!-- Soucty -->
  <div class="totals-wrapper">
    <div class="totals">
      <div class="detail-rows">
        <div class="row">
          <span class="label">${escapeHtml(t.subtotal)}</span>
          <span class="value">${formatNum(subtotal)} ${escapeHtml(currency)}</span>
        </div>${vatDetailHtml}
      </div>
      <div class="grand">
        <span class="label">${escapeHtml(t.total)}</span>
        <span class="value">${formatNum(totalToPay)} ${escapeHtml(currency)}</span>
      </div>
      <div class="currency-note">${escapeHtml(t.amounts_in)} ${escapeHtml(currency)}</div>
    </div>
  </div>

  ${sectionsHtml}

</div><!-- /.invoice-content -->
<div class="invoice-footer">

  <!-- Vystavil tiskne Puppeteer footerTemplate na kazde strance -->

  <!-- Upozorneni -->
  <div class="notice">
    ${escapeHtml(t.notice)}
  </div>

  <!-- DPH rekapitulace + QR -->
  <div class="recap-section">
    <div class="qr">
      ${qrSvg}
    </div>
    <table>
      <thead>
        <tr>
          <th colspan="4">${escapeHtml(t.vat_recap)}</th>
        </tr>
        <tr>
          <th>${escapeHtml(t.vat_base)}</th>
          <th>${escapeHtml(t.vat_rate)}</th>
          <th>${escapeHtml(t.vat_amount)}</th>
          <th>${escapeHtml(t.vat_with_total)}</th>
        </tr>
      </thead>
      <tbody>
        ${vatRecapHtml}
      </tbody>
      ${
        isForeign
          ? `<tfoot>
        <tr>
          <td colspan="4" style="font-size:0.7em; color:#666; padding-top:6px; text-align:left;">
            ${escapeHtml(t.cnb_rate)} ${formatDate(invoice.issue_date)}: 1 ${escapeHtml(currency)} = ${cnbRate.toFixed(3).replace(".", ",")} CZK
          </td>
        </tr>
      </tfoot>`
          : ""
      }
    </table>
  </div>

  <!-- Prevzal / razitko -->
  <div class="footer-row">
    <div class="col">${escapeHtml(t.received_by)}</div>
    <div class="col" style="text-align:right">${escapeHtml(t.stamp)}</div>
  </div>

</div><!-- /.invoice-footer -->
</div><!-- /.invoice-page -->

</body>
</html>`;

        const saveMode = query.save === "1";

        // Save PDF to NAS
        if (
          saveMode &&
          nasInvoicesManager.isConfigured() &&
          invoice.invoice_number
        ) {
          const issueDate = invoice.issue_date
            ? new Date(invoice.issue_date)
            : new Date();
          nasInvoicesManager.cleanIssued(invoice.invoice_number!);
          // Per-page "Vystavil + Strana X z Y" footer (same as issued
          // orders); the supplier e-mail rides along on the left (it used to
          // print under the body Vystavil block).
          const issuerLine = `${invoice.issued_by || ""}${
            suppEmail ? `${invoice.issued_by ? " · " : ""}${suppEmail}` : ""
          }`;
          const pdfBuffer = await htmlToPdf(html, {
            headerTemplate: buildInvoicePdfHeader(
              lang,
              logoDataUri,
              invoice.invoice_number || "",
            ),
            footerTemplate: buildInvoicePdfFooter(lang, issuerLine),
          });
          nasInvoicesManager.saveIssuedPdf(
            invoice.invoice_number!,
            issueDate.getFullYear(),
            issueDate.getMonth() + 1,
            pdfBuffer,
          );
          return success(reply, null, 200, "PDF uloženo");
        }

        return reply.type("text/html").send(html);
      } catch (err) {
        request.log.error(err, "PDF generation failed");
        return reply
          .status(500)
          .type("text/html")
          .send("<html><body><h1>Chyba při generování PDF</h1></body></html>");
      }
    },
  );
}
