import { FastifyInstance } from "fastify";
import type { Prisma, company_settings } from "@prisma/client";
import prisma from "../../config/database";
import { requirePermission } from "../../middleware/auth";
import { nasOffersManager } from "../../services/nas-offers-manager";
import { htmlToPdf } from "../../utils/html-to-pdf";
import { parseId, success } from "../../utils/response";
import {
  formatDate,
  formatNum,
  formatCurrency,
  escapeHtml,
  cleanQuillHtml,
  buildQuillIndentCss,
} from "../../utils/pdf-shared";
import createDOMPurify from "dompurify";
import { JSDOM } from "jsdom";

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

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

function buildAddressLines(
  entity: Record<string, unknown> | null,
  isSupplier: boolean,
  t: (key: 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;
      }
    }
  }

  // Legacy PascalCase key compat
  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 = `${t("ico")}: ${entity.company_id}`;
  if (entity.vat_id) fieldMap.vat_id = `${t("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 > 0) {
    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 };
}

const TRANSLATIONS: Record<string, Record<string, string>> = {
  title: { EN: "PRICE QUOTATION", CZ: "CENOV\u00C1 NAB\u00CDDKA" },
  valid_until: { EN: "Valid until", CZ: "Platnost do" },
  customer: { EN: "Customer", CZ: "Z\u00E1kazn\u00EDk" },
  supplier: { EN: "Supplier", CZ: "Dodavatel" },
  no: { EN: "N.", CZ: "\u010C." },
  description: { EN: "Description", CZ: "Popis" },
  qty: { EN: "Qty", CZ: "Mn." },
  unit_price: { EN: "Unit Price", CZ: "Jedn. cena" },
  included: { EN: "Included", CZ: "Zahrnuto" },
  total: { EN: "Total", CZ: "Celkem" },
  total_no_vat: { EN: "Total excl. VAT", CZ: "Celkem bez DPH" },
  ico: { EN: "ID", CZ: "I\u010CO" },
  dic: { EN: "VAT ID", CZ: "DI\u010C" },
  page: { EN: "Page", CZ: "Strana" },
  of: { EN: "of", CZ: "z" },
};

/** Quotation row with the relations the PDF template renders. */
export type OfferForPdf = Prisma.quotationsGetPayload<{
  include: {
    customers: true;
    quotation_items: true;
    scope_sections: true;
  };
}>;

/**
 * Build the full offer PDF/preview HTML. Extracted from the route handler so
 * the offers /:id/file live-render fallback (quotations.ts) can reuse the
 * exact same template without going through HTTP.
 */
export function renderOfferHtml(
  quotation: OfferForPdf,
  settings: company_settings | null,
): string {
  const isCzech = (quotation.language ?? "EN") !== "EN";
  const langKey = isCzech ? "CZ" : "EN";
  const currency = quotation.currency || "EUR";
  const t = (key: string): string => TRANSLATIONS[key]?.[langKey] || key;

  let logoImg = "";
  if (settings?.logo_data) {
    const buf = Buffer.from(settings.logo_data);
    let mime = "image/png";
    if (buf[0] === 0xff && buf[1] === 0xd8) mime = "image/jpeg";
    else if (buf[0] === 0x47 && buf[1] === 0x49) mime = "image/gif";
    else if (buf[0] === 0x52 && buf[1] === 0x49) mime = "image/webp";
    logoImg = `<img src="data:${escapeHtml(mime)};base64,${buf.toString("base64")}" class="logo" />`;
  }

  // Offers are NOT tax documents — the total is NET only (no VAT math).
  const items = quotation.quotation_items;
  let total = 0;
  for (const item of items) {
    if (item.is_included_in_total !== false) {
      total += (Number(item.quantity) || 0) * (Number(item.unit_price) || 0);
    }
  }
  // Visibility uses the SAME per-language title rule as the render loop plus
  // a tag-stripped content check (an empty-Quill "<p><br></p>" doesn't count)
  // — otherwise a section with only the other language's title would count as
  // content and produce a scope page holding nothing but the header. Mirrors
  // issued-orders-pdf.
  const stripSectionTags = (html: string | null | undefined): string =>
    (html || "")
      .replace(/<[^>]*>/g, "")
      .replace(/&nbsp;/g, " ")
      .trim();
  const resolveSectionTitle = (s: {
    title: string | null;
    title_cz: string | null;
  }): string =>
    isCzech && (s.title_cz || "").trim() ? s.title_cz || "" : s.title || "";
  const visibleSections = quotation.scope_sections.filter(
    (s) => resolveSectionTitle(s).trim() || stripSectionTags(s.content),
  );
  const hasScopeContent = visibleSections.length > 0;

  const cust = buildAddressLines(
    quotation.customers as unknown as Record<string, unknown>,
    false,
    t,
  );
  const supp = buildAddressLines(
    settings as unknown as Record<string, unknown>,
    true,
    t,
  );

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

  // Indentation CSS for Quill
  const indentCSS = buildQuillIndentCss();

  let itemsHtml = "";
  items.forEach((item, i) => {
    const qty = Number(item.quantity) || 0;
    const lineTotal = qty * (Number(item.unit_price) || 0);
    // Integers render without decimals; fractional quantities keep 2 decimals
    // (the old toFixed(0) ROUNDED 1.5 → 2 on the printed document).
    const qtyDecimals = Math.floor(qty) === qty ? 0 : 2;
    const subDesc = item.item_description || "";
    const evenClass = i % 2 === 1 ? ' class="even"' : "";
    itemsHtml += `<tr${evenClass}>
            <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 || "").trim() ? ` / ${escapeHtml((item.unit || "").trim())}` : ""}</td>
            <td class="right">${formatCurrency(Number(item.unit_price) || 0, currency)}</td>
            <td class="right total-cell">${formatCurrency(lineTotal, currency)}</td>
          </tr>`;
  });

  // No Mezisoučet/VAT rows — they would only duplicate the net total.
  const totalsHtml = `<div class="grand">
      <span class="label">${escapeHtml(t("total_no_vat"))}</span>
      <span class="value">${formatCurrency(total, currency)}</span>
    </div>`;
  const quotationNumber = escapeHtml(quotation.quotation_number);

  let scopeHtml = "";
  if (hasScopeContent) {
    scopeHtml += '<div class="scope-page">';
    scopeHtml += `<div class="page-header">
          <div class="left">
            <div class="page-title">${escapeHtml(t("title"))}</div>
            <div class="quotation-number">${quotationNumber}</div>
            ${quotation.project_code ? `<div class="project-code">${escapeHtml(quotation.project_code)}</div>` : ""}
            <div class="valid-until">${escapeHtml(t("valid_until"))}: ${escapeHtml(formatDate(quotation.valid_until))}</div>
          </div>
          ${logoImg ? `<div class="right">${logoImg}</div>` : ""}
        </div>
        <hr class="separator" />`;

    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>";
  }

  const pageLabel = escapeHtml(t("page"));
  const ofLabel = escapeHtml(t("of"));

  const html = `<!DOCTYPE html>
<html lang="${isCzech ? "cs" : "en"}">
<head>
<meta charset="utf-8" />
<title>${quotationNumber}</title>
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<link rel="shortcut icon" href="/favicon.ico">
<style>
  /* ---- Base ---- */
  @page {
    size: A4;
    margin: 15mm 15mm 25mm 15mm;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    font-family: "Segoe UI", Tahoma, Arial, sans-serif;
    font-size: 10pt;
    color: #1a1a1a;
    width: 180mm;
  }

  img, table, pre, code { max-width: 100%; }

  /* ---- Quill font classes – v PDF vynuceno Tahoma ---- */
  [class*="ql-font-"] { font-family: Tahoma, sans-serif !important; }

  /* ---- Quill alignment ---- */
  .ql-align-center  { text-align: center; }
  .ql-align-right   { text-align: right; }
  .ql-align-justify { text-align: justify; }

  /* ---- Quill indentation ---- */
${indentCSS}

  /* ---- Page header ---- */
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 4mm;
  }
  .page-header .left { flex: 1; }
  .page-header .right { flex-shrink: 0; margin-left: 10mm; }
  .logo { max-width: 42mm; max-height: 22mm; object-fit: contain; }

  .page-title {
    font-size: 18pt;
    font-weight: bold;
    color: #1a1a1a;
    margin: 0;
  }
  .scope-page .page-title { font-size: 16pt; }
  .quotation-number {
    font-size: 12pt;
    color: #1a1a1a;
    margin: 1mm 0;
  }
  .project-code {
    font-size: 10pt;
    color: #646464;
  }
  .valid-until {
    font-size: 9pt;
    color: #646464;
    margin-top: 1mm;
  }
  .scope-subtitle {
    font-size: 11pt;
    color: #646464;
    margin-top: 1mm;
  }
  .scope-description {
    font-size: 9pt;
    color: #646464;
    margin-top: 1mm;
  }

  .separator {
    border: none;
    border-top: 0.5pt solid #e0e0e0;
    margin: 3mm 0 5mm 0;
  }

  /* ---- Addresses ---- */
  .addresses {
    display: flex;
    justify-content: space-between;
    margin-bottom: 8mm;
  }
  .address-block { width: 48%; }
  .address-block.right { text-align: right; }
  .address-label {
    font-size: 9pt;
    font-weight: bold;
    color: #646464;
    line-height: 1.5;
  }
  .address-name {
    font-size: 9pt;
    font-weight: bold;
    color: #1a1a1a;
    line-height: 1.5;
  }
  .address-line {
    font-size: 9pt;
    color: #646464;
    line-height: 1.5;
  }

  /* ---- Items table ---- */
  table.items {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 9pt;
    margin-bottom: 2mm;
  }
  table.items thead th {
    font-size: 8pt;
    font-weight: 600;
    color: #646464;
    padding: 6px 8px;
    text-align: left;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    border-bottom: 1pt solid #1a1a1a;
  }
  table.items thead th.center { text-align: center; }
  table.items thead th.right  { text-align: right; }

  table.items tbody td {
    padding: 7px 8px;
    border-bottom: 0.5pt solid #e0e0e0;
    vertical-align: middle;
    word-wrap: break-word;
    overflow-wrap: break-word;
    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: 8pt;
  }
  table.items tbody td.desc {
    font-size: 10pt;
    font-weight: 600;
    color: #1a1a1a;
  }
  table.items tbody td.total-cell {
    font-weight: 700;
  }
  .item-subdesc {
    font-size: 9pt;
    color: #646464;
    margin-top: 2px;
    font-weight: 400;
  }

  /* ---- Totals ---- */
  .totals-wrapper {
    display: flex;
    justify-content: flex-end;
    break-inside: avoid;
    margin-top: 8mm;
  }
  .totals {
    width: 80mm;
  }
  .totals .detail-rows {
    margin-bottom: 3mm;
  }
  .totals .row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 8.5pt;
    color: #646464;
    margin-bottom: 2mm;
  }
  .totals .row:last-child { margin-bottom: 0; }
  .totals .row .value {
    color: #1a1a1a;
    font-size: 8.5pt;
  }
  .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: 9.5pt;
    font-weight: 400;
    color: #646464;
    align-self: center;
  }
  .totals .grand .value {
    font-size: 14pt;
    font-weight: 600;
    color: #1a1a1a;
    border-bottom: 2.5pt solid #de3a3a;
    padding-bottom: 1mm;
  }

  /* ---- Scope sections ---- */
  .scope-page {
    page-break-before: always;
  }
  .scope-section {
    width: 100%;
    max-width: 100%;
    margin-bottom: 3mm;
    break-inside: avoid;
  }
  .scope-section-title {
    font-size: 11pt;
    font-weight: bold;
    color: #1a1a1a;
    margin-bottom: 1mm;
  }
  .section-content {
    font-size: 9pt;
    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; }

  /* ---- Repeating page header ---- */
  table.page-layout {
    width: 100%;
    border-collapse: collapse;
  }
  table.page-layout > thead > tr > td,
  table.page-layout > tbody > tr > td {
    padding: 0;
    border: none;
    vertical-align: top;
  }
  /* ---- Page break helpers ---- */
  table.page-layout thead { display: table-header-group; }
  table.items tbody tr { break-inside: avoid; }

  @media print {
    body {
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    @page {
      @bottom-center {
        content: "${pageLabel} " counter(page) " ${ofLabel} " counter(pages);
        font-size: 8pt;
        color: #969696;
        font-family: "Segoe UI", Tahoma, Arial, sans-serif;
      }
    }
  }

  /* ---- Screen-only: A4 page preview ---- */
  @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;
    }
    .quotation-page, .scope-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;
    }
    table.page-layout,
    table.page-layout > thead,
    table.page-layout > thead > tr,
    table.page-layout > thead > tr > td,
    table.page-layout > tbody,
    table.page-layout > tbody > tr,
    table.page-layout > tbody > tr > td {
      display: block;
      width: 100%;
    }
  }
</style>
</head>
<body>

  <!-- ============ QUOTATION (full header in thead repeats on every page) ============ -->
  <div class="quotation-page">
  <table class="page-layout">
    <thead>
      <tr><td>
        <div class="page-header">
          <div class="left">
            <div class="page-title">${escapeHtml(t("title"))}</div>
            <div class="quotation-number">${quotationNumber}</div>
            ${quotation.project_code ? `<div class="project-code">${escapeHtml(quotation.project_code)}</div>` : ""}
            <div class="valid-until">${escapeHtml(t("valid_until"))}: ${escapeHtml(formatDate(quotation.valid_until))}</div>
          </div>
          ${logoImg ? `<div class="right">${logoImg}</div>` : ""}
        </div>
        <hr class="separator" />
      </td></tr>
    </thead>
    <tbody>
      <tr><td>
        <div class="addresses">
          <div class="address-block left">
            <div class="address-label">${escapeHtml(t("customer"))}</div>
            <div class="address-name">${escapeHtml(cust.name)}</div>
            ${custLinesHtml}
          </div>
          <div class="address-block right">
            <div class="address-label">${escapeHtml(t("supplier"))}</div>
            <div class="address-name">${escapeHtml(supp.name)}</div>
            ${suppLinesHtml}
          </div>
        </div>

        <table class="items">
          <thead>
            <tr>
              <th class="center" style="width:5%">${escapeHtml(t("no"))}</th>
              <th style="width:44%">${escapeHtml(t("description"))}</th>
              <th class="center" style="width:13%">${escapeHtml(t("qty"))}</th>
              <th class="right" style="width:18%">${escapeHtml(t("unit_price"))}</th>
              <th class="right" style="width:20%">${escapeHtml(t("total"))}</th>
            </tr>
          </thead>
          <tbody>
            ${itemsHtml}
          </tbody>
        </table>

        <div class="totals-wrapper">
          <div class="totals">
            ${totalsHtml}
          </div>
        </div>
      </td></tr>
    </tbody>
  </table>
  </div>

  ${scopeHtml}

</body>
</html>`;

  return html;
}

export default async function offersPdfRoutes(
  fastify: FastifyInstance,
): Promise<void> {
  fastify.get<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requirePermission("offers.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 quotation = await prisma.quotations.findUnique({
          where: { id },
          include: {
            customers: true,
            quotation_items: { orderBy: { position: "asc" } },
            scope_sections: { orderBy: [{ position: "asc" }, { id: "asc" }] },
          },
        });

        if (!quotation) {
          return reply
            .status(404)
            .type("text/html")
            .send("<html><body><h1>Nabídka nenalezena</h1></body></html>");
        }

        const settings = await prisma.company_settings.findFirst();
        const html = renderOfferHtml(quotation, settings);

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

        // Save PDF to NAS
        if (
          saveMode &&
          nasOffersManager.isConfigured() &&
          quotation.quotation_number
        ) {
          const created = quotation.created_at
            ? new Date(quotation.created_at)
            : new Date();
          const pdfBuffer = await htmlToPdf(html);
          nasOffersManager.saveOfferPdf(
            quotation.quotation_number!,
            created.getFullYear(),
            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\u0159i generov\u00E1n\u00ED PDF</h1></body></html>",
          );
      }
    },
  );
}
