/**
 * Output renderers for the reports module.
 *
 * Every report's `run` returns `{ columns, rows, totals?, subtitle? }`.
 * CSV/XLSX/PDF all derive from the same shape so adding a report = one
 * registry entry, no per-format template.
 *
 * PDF uses pdfkit. The statement format (edo-cuenta-datos) uses a
 * different layout than the tabular one — handled inline.
 */

// pdfkit exports its constructor via `module.exports = PDFDocument`, so a
// namespace import gets the type, and `import = require()` gets the value.
import PDFDocument = require("pdfkit");
import * as ExcelJS from "exceljs";
import type { ColumnDef, ReportResult } from "./reports.types";
import { getCompanyInfo } from "./company";

type Doc = PDFKit.PDFDocument;

/* ----------------------------------------------------------------- CSV */

function csvCell(v: unknown): string {
  if (v === null || v === undefined) return "";
  const s = String(v);
  if (s.includes(",") || s.includes('"') || s.includes("\n") || s.includes("\r")) {
    return `"${s.replace(/"/g, '""')}"`;
  }
  return s;
}

export function renderCsv(columns: ColumnDef[], result: ReportResult): string {
  const headers = columns.map((c) => csvCell(c.label)).join(",");
  const lines = result.rows.map((r) =>
    columns
      .map((c) => {
        const v = r[c.key];
        if (typeof v === "number") return v;
        return csvCell(v);
      })
      .join(","),
  );
  const totals: string[] = [];
  if (result.totals) {
    for (const [k, v] of Object.entries(result.totals)) {
      totals.push(csvCell(k), csvCell(v));
    }
  }
  return [headers, ...lines, ...(totals.length ? [totals.join(",")] : [])].join(
    "\n",
  );
}

/* ----------------------------------------------------------------- XLSX */

export async function renderXlsx(
  columns: ColumnDef[],
  result: ReportResult,
): Promise<Buffer> {
  const wb = new ExcelJS.Workbook();
  wb.creator = "Jorge Cuadros & Asociados";
  const ws = wb.addWorksheet("Reporte", {
    views: [{ state: "frozen", ySplit: 1 }],
  });
  ws.columns = columns.map((c) => ({
    header: c.label,
    key: c.key,
    width: Math.max(10, Math.min(40, (c.label.length + 2) * 1.2)),
  }));
  ws.getRow(1).font = { bold: true };
  ws.getRow(1).fill = {
    type: "pattern",
    pattern: "solid",
    fgColor: { argb: "FFE2EDE9" }, // brand-tint
  };
  for (const row of result.rows) {
    ws.addRow(row);
  }
  // Number formatting for money columns.
  for (const col of columns) {
    if (col.type === "money" || col.type === "number") {
      ws.getColumn(col.key).numFmt =
        col.type === "money" ? "#,##0.00" : "#,##0";
      ws.getColumn(col.key).alignment = { horizontal: "right" };
    }
  }
  if (result.totals) {
    const last = ws.addRow({});
    let i = 1;
    for (const [k, v] of Object.entries(result.totals)) {
      const cell = ws.getCell(last.number, i);
      cell.value = `${k}: ${v}`;
      cell.font = { bold: true };
      i++;
    }
  }
  const buf = await wb.xlsx.writeBuffer();
  return Buffer.from(buf);
}

/* ----------------------------------------------------------------- PDF */

const BRAND = "#0c322d";
const ACCENT = "#bf5a34";
const MUTED = "#756c5c";
const LINE = "#e4dccb";

function fmtMoney(v: unknown): string {
  if (v === null || v === undefined || v === "") return "";
  const n = Number(v);
  if (!Number.isFinite(n)) return String(v);
  return n.toLocaleString("es-MX", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function pdfRow(
  doc: Doc,
  y: number,
  cols: Array<{ label: string; width: number; align?: "left" | "right" }>,
  values: Array<{ text: string; align?: "left" | "right" }>,
  x: number,
): number {
  let cx = x;
  for (let i = 0; i < cols.length; i++) {
    const c = cols[i];
    const v = values[i] ?? { text: "" };
    const align = v.align ?? c.align ?? "left";
    const w = c.width;
    doc
      .font("Helvetica")
      .fontSize(9)
      .fillColor("#211d17")
      .text(v.text, cx, y, {
        width: w - 4,
        align,
        ellipsis: true,
        lineBreak: false,
        height: 16,
      });
    cx += w;
  }
  return y + 18;
}

export function renderPdf(
  columns: ColumnDef[],
  result: ReportResult,
  title: string,
): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({
      size: "LETTER",
      layout: "landscape",
      margins: { top: 96, bottom: 56, left: 48, right: 48 },
      bufferPages: true,
      info: {
        Title: title,
        Author: "Jorge Cuadros & Asociados",
        Subject: "Reporte",
        Creator: "Jorge Cuadros Platform — Reports module",
      },
    });
    const chunks: Buffer[] = [];
    doc.on("data", (c: Buffer) => chunks.push(c));
    doc.on("end", () => resolve(Buffer.concat(chunks)));
    doc.on("error", reject);

    const company = getCompanyInfo();
    const pageW = doc.page.width - 96;

    /** The header is repeated on every page (via addPage + manual draw). */
    const drawHeader = () => {
      // Background bar (brand pine) for the masthead.
      doc.rect(0, 0, doc.page.width, 60).fill(BRAND);

      // Logo, fitted to a 40px box, with 8px padding.
      let textX = 48;
      if (company.logo) {
        const targetH = 40;
        const scale = targetH / company.logo.height;
        const w = company.logo.width * scale;
        doc.image(company.logo.buffer, 48, 10, { height: targetH });
        textX = 48 + w + 14;
      }

      // Company name (large) + "Reporte" tag below it.
      doc
        .fillColor("#f5f1e8")
        .font("Helvetica-Bold")
        .fontSize(15)
        .text(company.name, textX, 14, { width: pageW - (textX - 48), lineBreak: false });
      doc
        .font("Helvetica")
        .fontSize(8)
        .fillColor("#cde0db")
        .text("Reporte", textX, 36, { lineBreak: false });

      // Right-aligned company locator (address + phone + email).
      const rightLines = [
        company.addressLine1,
        company.addressLine2,
        [company.cityState].filter(Boolean).join(" · "),
        [company.phone, company.email].filter(Boolean).join(" · "),
        company.taxId ? `RFC: ${company.taxId}` : "",
      ].filter(Boolean);
      doc.font("Helvetica").fontSize(8).fillColor("#cde0db");
      let ry = 12;
      for (const line of rightLines) {
        doc.text(line, 48, ry, {
          width: pageW,
          align: "right",
          lineBreak: false,
          ellipsis: true,
        });
        ry += 10;
      }

      // Thin accent line under the masthead.
      doc.rect(0, 60, doc.page.width, 2).fill(ACCENT);

      // Title + subtitle + printed-at.
      doc
        .font("Helvetica-Bold")
        .fontSize(15)
        .fillColor(BRAND)
        .text(title, 48, 72, { lineBreak: false });
      let metaY = 92;
      if (result.subtitle) {
        doc
          .font("Helvetica")
          .fontSize(9)
          .fillColor(MUTED)
          .text(result.subtitle, 48, metaY, { lineBreak: false });
        metaY += 12;
      }
      const printedAt = new Date().toLocaleString("es-MX");
      doc
        .font("Helvetica")
        .fontSize(8)
        .fillColor(MUTED)
        .text(`Impreso: ${printedAt}`, 48, metaY, { lineBreak: false });
    };

    drawHeader();

    // Column widths: distribute page width minus margins, weighted.
    const totalW = columns.reduce((s, c) => s + (c.width ?? 12), 0);
    const cols = columns.map((c) => ({
      label: c.label,
      width: ((c.width ?? 12) / totalW) * pageW,
      align: c.align,
    }));

    let y = 130;
    const drawTableHeader = () => {
      doc.rect(48, y, pageW, 18).fill("#faf6ee");
      y = pdfRow(
        doc,
        y + 4,
        cols,
        cols.map((c) => ({ text: c.label, align: c.align })),
        48,
      );
      doc
        .moveTo(48, y)
        .lineTo(48 + pageW, y)
        .strokeColor(LINE)
        .lineWidth(0.5)
        .stroke();
    };
    drawTableHeader();

    // Body rows.
    for (const r of result.rows) {
      if (y > doc.page.height - 64) {
        doc.addPage({ layout: "landscape", margins: { top: 96, bottom: 56, left: 48, right: 48 } });
        drawHeader();
        y = 130;
        drawTableHeader();
      }
      const vals = columns.map((c) => {
        const v = r[c.key];
        const text = c.type === "money" ? fmtMoney(v) : v == null ? "" : String(v);
        return { text, align: c.align };
      });
      y = pdfRow(doc, y + 4, cols, vals, 48);
      doc
        .moveTo(48, y)
        .lineTo(48 + pageW, y)
        .strokeColor("#e4dccb")
        .lineWidth(0.4)
        .stroke();
    }

    // Totals.
    if (result.totals) {
      y += 6;
      doc.rect(48, y, pageW, 18).fill(ACCENT);
      doc
        .font("Helvetica-Bold")
        .fontSize(9)
        .fillColor("#f5f1e8")
        .text(
          Object.entries(result.totals)
            .map(([k, v]) => `${k}: ${v}`)
            .join("   ·   "),
          52,
          y + 5,
          { width: pageW - 8, align: "left" },
        );
    }

    doc.end();
  });
}

/* ----------------------------------------------------------------- print (HTML) */

/**
 * Print-stylesheet-friendly HTML. The web app's print stylesheet hides
 * nav, but otherwise this is a plain table the browser paginates itself.
 *
 * The header carries the company info (logo + name + locator) so printed
 * pages stand alone — staff can hand one to a customer and the office
 * identification is on every sheet, not buried in the cover page.
 */
export function renderPrintHtml(
  columns: ColumnDef[],
  result: ReportResult,
  title: string,
): string {
  const company = getCompanyInfo();
  const head = (label: string, align?: "left" | "right") =>
    `<th style="text-align:${align ?? "left"};padding:6px 8px;border-bottom:2px solid #0c322d;background:#faf6ee;font-size:11px">${escapeHtml(label)}</th>`;
  const cell = (v: unknown, c: ColumnDef) => {
    const text = c.type === "money" ? fmtMoney(v) : v == null ? "" : String(v);
    const align = c.align ?? "left";
    return `<td style="text-align:${align};padding:4px 8px;border-bottom:1px solid #e4dccb;font-size:11px;${c.type === "money" ? "font-variant-numeric:tabular-nums" : ""}">${escapeHtml(text)}</td>`;
  };
  const rows = result.rows
    .map(
      (r) =>
        `<tr>${columns
          .map((c) => cell(r[c.key], c))
          .join("")}</tr>`,
    )
    .join("");
  const totals = result.totals
    ? `<tr><td colspan="${columns.length}" style="padding:8px;background:#bf5a34;color:#f5f1e8;font-weight:600;font-size:11px">${Object.entries(
        result.totals,
      )
        .map(([k, v]) => `${escapeHtml(k)}: ${escapeHtml(String(v))}`)
        .join(" &nbsp;·&nbsp; ")}</td></tr>`
    : "";

  // Logo embedded as base64 data URL — the print page is opened as a
  // new tab and printed standalone, so a relative path to the web app
  // wouldn't resolve when launched outside the web's origin.
  const logoDataUrl = company.logo
    ? `data:image/png;base64,${company.logo.buffer.toString("base64")}`
    : null;

  const locatorLines = [
    company.addressLine1,
    company.addressLine2,
    company.cityState,
    [company.phone, company.email].filter(Boolean).join(" · "),
    company.taxId ? `RFC: ${company.taxId}` : "",
  ].filter(Boolean);

  return `<!doctype html>
<html lang="es"><head>
<meta charset="utf-8" />
<title>${escapeHtml(title)} — ${escapeHtml(company.name)}</title>
<style>
  @page { size: letter landscape; margin: 0.5in; }
  body { font-family: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif; color: #211d17; margin: 0; }
  .masthead { display: flex; align-items: flex-start; gap: 16px; padding: 12px 16px; background: #0c322d; color: #f5f1e8; border-radius: 6px 6px 0 0; }
  .masthead-logo { flex: 0 0 auto; }
  .masthead-logo img { display: block; height: 56px; width: auto; }
  .masthead-text { flex: 1; min-width: 0; }
  .masthead-name { font-family: Georgia, "Times New Roman", serif; font-size: 20px; font-weight: 600; line-height: 1.1; }
  .masthead-tag { font-size: 11px; color: #cde0db; margin-top: 2px; text-transform: uppercase; letter-spacing: 0.06em; }
  .masthead-locator { font-size: 10px; color: #cde0db; text-align: right; line-height: 1.35; white-space: nowrap; }
  .accent { height: 3px; background: #bf5a34; }
  .head { padding: 12px 4px 8px; }
  .head h1 { font-family: Georgia, "Times New Roman", serif; font-size: 18px; margin: 0; color: #0c322d; }
  .head p  { font-size: 11px; color: #756c5c; margin: 2px 0 0; }
  table { width: 100%; border-collapse: collapse; }
  @media print {
    .noprint { display: none; }
    .masthead { border-radius: 0; }
  }
  .noprint { padding: 8px 0; }
  .noprint button { padding: 6px 12px; background: #0c322d; color: #f5f1e8; border: 0; border-radius: 4px; cursor: pointer; font-size: 12px; }
  .footer { margin-top: 16px; font-size: 9px; color: #756c5c; border-top: 1px solid #e4dccb; padding-top: 6px; display: flex; justify-content: space-between; }
</style>
</head><body>
<div class="noprint"><button onclick="window.print()">Imprimir / Guardar PDF</button></div>
<div class="masthead">
  ${logoDataUrl ? `<div class="masthead-logo"><img src="${logoDataUrl}" alt="" /></div>` : ""}
  <div class="masthead-text">
    <div class="masthead-name">${escapeHtml(company.name)}</div>
    <div class="masthead-tag">Reporte</div>
  </div>
  <div class="masthead-locator">
    ${locatorLines.map((l) => escapeHtml(l)).join("<br/>")}
    ${company.website ? `<br/>${escapeHtml(company.website)}` : ""}
  </div>
</div>
<div class="accent"></div>
<div class="head">
  <h1>${escapeHtml(title)}</h1>
  ${result.subtitle ? `<p>${escapeHtml(result.subtitle)}</p>` : ""}
  <p>Impreso: ${new Date().toLocaleString("es-MX")}</p>
</div>
<table>
  <thead><tr>${columns.map((c) => head(c.label, c.align)).join("")}</tr></thead>
  <tbody>${rows}${totals}</tbody>
</table>
<div class="footer">
  <span>${escapeHtml(company.name)} · ${escapeHtml(company.phone)} · ${escapeHtml(company.email)}</span>
  <span>${escapeHtml(title)}</span>
</div>
</body></html>`;
}

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}
