import { FastifyInstance } from "fastify";
import prisma from "../../config/database";
import { requirePermission } from "../../middleware/auth";
import { parseId, success } from "../../utils/response";
import { htmlToPdf } from "../../utils/html-to-pdf";
import { nasOrdersManager } from "../../services/nas-financials-manager";
import {
  formatDate,
  formatNum,
  formatCurrency,
  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 — shared with the other PDF routes via utils/pdf-shared ── */

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

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

  const nameKey = isCompany ? "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 };
}

/**
 * Address block for the sklad_suppliers counterparty — full customer model
 * (structured street/city/postal_code/country + custom_fields JSON; the old
 * dedicated address/contact columns were dropped 2026-06). IČO/DIČ come from
 * the supplier's ico/dic columns, prefixed with the same translated labels
 * the customer block uses; custom fields render as appended lines in array
 * order (suppliers have no PDF field-order picker).
 */
function buildSupplierLines(
  supplier: Record<string, unknown> | null,
  tObj: Record<string, string>,
): AddressResult {
  if (!supplier) return { name: "", lines: [] };
  const name = String(supplier.name || "");
  const lines: string[] = [];
  if (supplier.street) lines.push(String(supplier.street));
  const cityLine = [supplier.postal_code, supplier.city]
    .filter(Boolean)
    .map(String)
    .join(" ")
    .trim();
  if (cityLine) lines.push(cityLine);
  if (supplier.country) lines.push(String(supplier.country));
  if (supplier.ico) lines.push(`${tObj.ico}${supplier.ico}`);
  if (supplier.dic) lines.push(`${tObj.dic}${supplier.dic}`);

  // Custom fields ("Vlastní pole") — same blob format as customers; malformed
  // JSON degrades to no extra lines rather than failing the PDF.
  if (supplier.custom_fields) {
    let parsed: unknown;
    try {
      parsed =
        typeof supplier.custom_fields === "string"
          ? JSON.parse(supplier.custom_fields)
          : supplier.custom_fields;
    } catch {
      parsed = null;
    }
    const fields =
      parsed && typeof parsed === "object" && !Array.isArray(parsed)
        ? ((parsed as Record<string, unknown>).fields as Array<{
            name?: string;
            value?: string;
            showLabel?: boolean;
          }>)
        : Array.isArray(parsed)
          ? (parsed as Array<{
              name?: string;
              value?: string;
              showLabel?: boolean;
            }>)
          : [];
    for (const f of fields ?? []) {
      const value = (f?.value || "").trim();
      if (!value) continue;
      const label = (f?.name || "").trim();
      lines.push(
        f?.showLabel !== false && label ? `${label}: ${value}` : value,
      );
    }
  }
  return { name, lines };
}

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

type Lang = "cs" | "en";

const translations: Record<Lang, Record<string, string>> = {
  cs: {
    title: "OBJEDNÁVKA",
    heading: "OBJEDNÁVKA č.",
    // PO direction: WE (company) are the buyer (Odběratel), the
    // selected sklad_suppliers record is the supplier (Dodavatel).
    supplier: "Dodavatel",
    buyer: "Odběratel",
    issue_date: "Datum vystavení:",
    delivery_date: "Požadované dodání:",
    billing: "Objednáváme si u Vás:",
    col_no: "Č.",
    col_desc: "Popis",
    col_qty: "Množství",
    col_unit_price: "Jedn. cena",
    col_total: "Celkem",
    total_no_vat: "Celkem bez DPH",
    issued_by: "Vystavil:",
    ico: "IČ: ",
    dic: "DIČ: ",
  },
  en: {
    title: "PURCHASE ORDER",
    heading: "PURCHASE ORDER No.",
    supplier: "Supplier",
    buyer: "Buyer",
    issue_date: "Issue date:",
    delivery_date: "Requested delivery:",
    billing: "We order from you:",
    col_no: "No.",
    col_desc: "Description",
    col_qty: "Quantity",
    col_unit_price: "Unit price",
    col_total: "Total",
    total_no_vat: "Total excl. VAT",
    issued_by: "Issued by:",
    ico: "Reg. No.: ",
    dic: "Tax ID: ",
  },
};

interface IssuedOrderPdfData {
  po_number: string | null;
  order_date: Date | null;
  delivery_date: Date | null;
  currency: string | null;
  // Editable heading above the items table; null → t.billing default.
  order_text?: string | null;
}

interface IssuedOrderPdfItem {
  description: string | null;
  item_description: string | null;
  quantity: unknown;
  unit: string | null;
  unit_price: unknown;
}

export interface IssuedOrderPdfSection {
  title: string | null;
  title_cz: string | null;
  content: string | null;
}

/** 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 issued-order PDF, rendered by Puppeteer
 * in the bottom page margin (the only true repeating footer Chromium
 * supports): "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 buildIssuedOrderPdfFooter(
  lang: Lang,
  issuerName: string,
): string {
  const t = translations[lang];
  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(issuerName)}</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 issued-order PDF — the unified
 * red-accent family header (shared with invoices): logo left, red
 * "OBJEDNÁVKA č. 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 buildIssuedOrderPdfHeader(
  lang: Lang,
  logoDataUri: string,
  poNumber: string,
): string {
  const t = translations[lang];
  return buildPdfHeaderTemplate(logoDataUri, `${t.heading} ${poNumber}`);
}

export function renderIssuedOrderHtml(
  order: IssuedOrderPdfData,
  items: IssuedOrderPdfItem[],
  supplier: Record<string, unknown> | null,
  settings: Record<string, unknown> | null,
  lang: Lang,
  // Kept for signature stability across the many call sites: the issuer no
  // longer renders in the BODY — it prints in the Puppeteer footer template
  // (buildIssuedOrderPdfFooter) on every page.
  _issuer: { name: string },
  sections: IssuedOrderPdfSection[] = [],
): string {
  const t = translations[lang];
  const currency = order.currency || "CZK";
  const poNumber = escapeHtml(order.po_number || "");

  // Logo embedding — shared helper (also feeds the Puppeteer headerTemplate).
  const logoDataUri = logoDataUriFromSettings(settings);
  const logoImg = logoDataUri
    ? `<img src="${logoDataUri}" class="logo" />`
    : "";

  // PO direction: our company (settings) = Odběratel (buyer);
  // the sklad_suppliers record = Dodavatel (supplier).
  const buyer = buildAddressLines(settings, true, t); // company → Odběratel
  const supplierAddr = buildSupplierLines(supplier, t); // supplier → Dodavatel

  const buyerLinesHtml = buyer.lines
    .map((l) => `<div class="address-line">${escapeHtml(l)}</div>`)
    .join("");
  const supplierLinesHtml = supplierAddr.lines
    .map((l) => `<div class="address-line">${escapeHtml(l)}</div>`)
    .join("");

  // Items — NET only. A purchase order is not a tax document: no VAT columns,
  // no VAT math (same as the service computeIssuedOrderTotals).
  let total = 0;
  const itemsHtml = items
    .map((it, i) => {
      const qty = Number(it.quantity) || 0;
      const unitPrice = Number(it.unit_price) || 0;
      const lineTotal = qty * unitPrice;
      total += lineTotal;

      const qtyDecimals = Math.floor(qty) === qty ? 0 : 2;
      const descHtml = `${escapeHtml(it.description)}${
        it.item_description
          ? `<div class="item-sub">${escapeHtml(it.item_description)}</div>`
          : ""
      }`;
      return `<tr>
            <td class="row-num">${i + 1}</td>
            <td class="desc">${descHtml}</td>
            <td class="center">${formatNum(qty, qtyDecimals)}${it.unit ? ` / ${escapeHtml(it.unit)}` : ""}</td>
            <td class="right">${formatCurrency(unitPrice, currency)}</td>
            <td class="right total-cell">${formatCurrency(lineTotal, currency)}</td>
        </tr>`;
    })
    .join("");

  total = Math.round(total * 100) / 100;

  const issueDateStr = formatDate(order.order_date);
  const deliveryDateStr = formatDate(order.delivery_date);

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

  // ── Sections ("Obsah") — flow INLINE after the items/totals block (user
  // decision: no separate page, no repeated header). Each section keeps
  // break-inside: avoid; 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 (same rule as offers).
  const resolveSectionTitle = (s: IssuedOrderPdfSection): 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),
  );
  const hasSectionContent = visibleSections.length > 0;

  let scopeHtml = "";
  if (hasSectionContent) {
    scopeHtml += '<div class="scope-sections">';
    for (const section of visibleSections) {
      const title = resolveSectionTitle(section);
      const content = (section.content || "").trim();
      scopeHtml += '<div class="scope-section">';
      if (title.trim())
        scopeHtml += `<div class="scope-section-title">${escapeHtml(title)}</div>`;
      if (content)
        scopeHtml += `<div class="section-content">${cleanQuillHtml(DOMPurify.sanitize(content))}</div>`;
      scopeHtml += "</div>";
    }
    scopeHtml += "</div>";
  }

  return `<!DOCTYPE html>
<html lang="${escapeHtml(lang)}">
<head>
<meta charset="utf-8" />
<title>${escapeHtml(t.heading)} ${poNumber}</title>
<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 invoice 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;
  }

  /* The repeating "Vystavil / Strana X z Y" footer lives in Puppeteer's
     footerTemplate (bottom page margin), NOT in the body — the old flex
     min-height pinning is gone with it. */

  .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-supplier {
    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;
  }
  table.items tbody td.desc .item-sub {
    font-weight: 400;
    font-size: 8.5pt;
    color: #666;
    margin-top: 0.5mm;
  }
  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;
  }
  /* Upozorneni */
  .notice {
    font-size: 8pt;
    color: #1a1a1a;
    margin: 2mm 0;
    line-height: 1.3;
  }

  /* Sekce ("Obsah") — tecou inline za tabulkou polozek; zadna nova stranka,
     dlouhy obsah strankuje prirozene a paticku tiskne Puppeteer na kazdou
     stranu. */
  .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;
      gap: 30px;
      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)} ${poNumber}</div>
  </div>

  <!-- Odberatel (nase firma) / Dodavatel (sklad_suppliers) + Detaily -->
  <table class="header-grid" cellspacing="0">
    <tr>
      <td>
        <div class="address-label">${escapeHtml(t.buyer)}</div>
        <div class="address-name">${escapeHtml(buyer.name)}</div>
        ${buyerLinesHtml}
      </td>
      <td class="addr-supplier">
        <div class="address-label">${escapeHtml(t.supplier)}</div>
        <div class="address-name">${escapeHtml(supplierAddr.name)}</div>
        ${supplierLinesHtml}
      </td>
    </tr>
    <tr>
      <td class="details-bank">
        <div class="info-row"><span class="lbl">${escapeHtml(t.issue_date)}</span> <span class="val">${escapeHtml(issueDateStr) || "—"}</span></div>
        ${deliveryDateStr ? `<div class="info-row"><span class="lbl">${escapeHtml(t.delivery_date)}</span> <span class="val">${escapeHtml(deliveryDateStr)}</span></div>` : ""}
      </td>
      <td></td>
    </tr>
  </table>

  <!-- Polozky -->
  <div class="billing-label">${escapeHtml(order.order_text || t.billing)}</div>
  <table class="items">
    <thead>
      <tr>
        <th class="center" style="width:3%">${escapeHtml(t.col_no)}</th>
        <th style="width:56%">${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:21%">${escapeHtml(t.col_total)}</th>
      </tr>
    </thead>
    <tbody>
      ${itemsHtml}
    </tbody>
  </table>

  <!-- Soucty (jen souhrnny radek bez DPH - mezisoucet by ho jen opakoval;
       mena je soucasti castky pres formatCurrency, takze zadna "Částky jsou
       uvedeny v …" poznamka) -->
  <div class="totals-wrapper">
    <div class="totals">
      <div class="grand">
        <span class="label">${escapeHtml(t.total_no_vat)}</span>
        <span class="value">${formatCurrency(total, currency)}</span>
      </div>
    </div>
  </div>

  ${scopeHtml}

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

</body>
</html>`;
}

export default async function issuedOrdersPdfRoutes(fastify: FastifyInstance) {
  fastify.get<{ Params: { id: string } }>(
    "/:id",
    // Mirror invoices-pdf (invoices.view): view-level access serves BOTH the
    // user-facing Export PDF and the ?save=1 NAS archive. There is no separate
    // orders.export permission (it was dead — gated nothing — and was removed);
    // orders.view is the single gate for both paths.
    { preHandler: requirePermission("orders.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 order = await prisma.issued_orders.findUnique({ where: { id } });
        if (!order) {
          return reply
            .status(404)
            .type("text/html")
            .send("<html><body><h1>Objednávka nenalezena</h1></body></html>");
        }
        // Language comes from the document's own column; ?lang= is an
        // explicit override only (mirrors the /:id/file fallback semantics).
        const lang: Lang =
          query.lang === "en" || query.lang === "cs"
            ? query.lang
            : order.language === "en"
              ? "en"
              : "cs";
        const items = await prisma.issued_order_items.findMany({
          where: { issued_order_id: id },
          orderBy: { position: "asc" },
        });
        // id tiebreak: position can repeat, keep the order deterministic.
        const sections = await prisma.issued_order_sections.findMany({
          where: { issued_order_id: id },
          orderBy: [{ position: "asc" }, { id: "asc" }],
        });
        const supplier = order.supplier_id
          ? ((await prisma.sklad_suppliers.findUnique({
              where: { id: order.supplier_id },
            })) as Record<string, unknown> | null)
          : null;
        const settings = (await prisma.company_settings.findFirst()) as Record<
          string,
          unknown
        > | null;

        // Footer issuer = the logged-in user (there is no user FK on the
        // order). The route runs under requirePermission("orders.view") so
        // authData is always present here. Mirrors orders-pdf.ts userName.
        const issuer = {
          name: `${request.authData?.firstName ?? ""} ${request.authData?.lastName ?? ""}`.trim(),
        };

        const html = renderIssuedOrderHtml(
          order,
          items,
          supplier,
          settings,
          lang,
          issuer,
          sections,
        );

        // ?save=1 → archive the PDF to NAS instead of returning it (mirrors
        // invoices-pdf). A draft has no po_number → the guard skips it.
        const saveMode = query.save === "1";
        if (saveMode && nasOrdersManager.isConfigured() && order.po_number) {
          const baseDate = order.order_date
            ? new Date(order.order_date)
            : new Date();
          nasOrdersManager.cleanIssued(order.po_number);
          const pdfBuffer = await htmlToPdf(html, {
            headerTemplate: buildIssuedOrderPdfHeader(
              lang,
              logoDataUriFromSettings(settings),
              order.po_number || "",
            ),
            footerTemplate: buildIssuedOrderPdfFooter(lang, issuer.name),
          });
          nasOrdersManager.saveIssuedPdf(
            order.po_number,
            baseDate.getFullYear(),
            baseDate.getMonth() + 1,
            pdfBuffer,
          );
          return success(reply, null, 200, "PDF uloženo");
        }

        const pdf = await htmlToPdf(html, {
          headerTemplate: buildIssuedOrderPdfHeader(
            lang,
            logoDataUriFromSettings(settings),
            order.po_number || "",
          ),
          footerTemplate: buildIssuedOrderPdfFooter(lang, issuer.name),
        });
        const filename = `Objednavka-${order.po_number || String(id)}.pdf`;
        return reply
          .type("application/pdf")
          .header("Content-Disposition", `attachment; filename="${filename}"`)
          .send(pdf);
      } catch (err) {
        request.log.error(err, "Issued order PDF generation failed");
        return reply
          .status(500)
          .type("text/html")
          .send("<html><body><h1>Chyba při generování PDF</h1></body></html>");
      }
    },
  );
}
