import { Injectable } from "@nestjs/common";

import { PdfRenderService } from "./pdf-render.service";
import {
  PdfColor,
  assembleSinglePagePdf,
  lineOp,
  rectOp,
  sealOp,
  textOp,
  textOpRight,
  wrapText,
} from "./styled-pdf.util";

export type InvoiceDocumentKind = "INVOICE" | "RECEIPT";

/** One billed line on the document (charge type / fee type agnostic). */
export interface InvoiceDocumentLine {
  description: string | null;
  /** Optional categorisation column (e.g. "Fee type" / "Charge type"). */
  category?: string | null;
  quantity?: number | null;
  unitRate?: number | null;
  amount?: number | null;
  currency?: string | null;
}

/** A labelled total row in the totals box; mark `grand` for the headline total. */
export interface InvoiceDocumentTotal {
  label: string;
  amount: number;
  grand?: boolean;
}

/**
 * Source-agnostic description of a printable invoice/receipt. Each billing
 * source maps its own entity onto this shape; the renderer owns the layout so
 * every EDR invoice document looks identical regardless of source.
 */
export interface InvoiceDocumentModel {
  kind: InvoiceDocumentKind;
  /** Document heading, e.g. "Warehouse Fee Invoice" / "Freight Invoice". */
  title: string;
  documentNumber: string;
  issuedAt?: Date | string | null;
  status: string;
  currency: string;
  /** Free-form summary grid (label/value pairs). */
  summary: Array<{ label: string; value: string | null }>;
  /** Header for the line-item category column; column hidden when omitted. */
  categoryHeader?: string;
  lines: InvoiceDocumentLine[];
  totals: InvoiceDocumentTotal[];
  /** Override the round seal text; defaults from kind/status. */
  sealText?: string;
}

/**
 * Central invoice/receipt PDF renderer shared by every billing source. Turns a
 * {@link InvoiceDocumentModel} into the sealed EDR document HTML and renders it
 * via {@link PdfRenderService}. Previously this layout lived (warehouse-only) in
 * `WarehouseInvoiceService`; it now serves all invoices.
 */
@Injectable()
export class InvoiceDocumentService {
  constructor(private readonly pdf: PdfRenderService) {}

  async render(
    model: InvoiceDocumentModel,
  ): Promise<{ filename: string; buffer: Buffer }> {
    const html = this.buildHtml(model);
    const kindLabel = model.kind === "RECEIPT" ? "receipt" : "invoice";
    return {
      filename: `${this.safeFilename(model.documentNumber)}-${kindLabel}.pdf`,
      buffer: await this.pdf.htmlToPdfBuffer(html, {
        label: `${model.title} ${kindLabel}`,
        // Chromium-less fallback: draw a genuine styled invoice (header, seal,
        // summary grid, line-item table, totals) from the model — not a flat
        // plain-text dump — so it still reads as a proper invoice document.
        fallback: () => this.buildFallbackPdf(model),
      }),
    };
  }

  /**
   * Vector-drawn styled invoice/receipt used when headless Chromium is
   * unavailable. Mirrors the HTML layout closely enough to pass as the same
   * document. Single A4 page; long summaries / line lists are capped to fit.
   */
  buildFallbackPdf(model: InvoiceDocumentModel): Buffer {
    const currency = (cur?: string | null) =>
      (cur ?? model.currency) === "ETB" ? "ETB" : (cur ?? model.currency);
    const money = (amount: unknown, cur?: string | null) =>
      `${Number(amount ?? 0).toLocaleString()} ${currency(cur)}`;
    const date = (value: unknown) =>
      value ? new Date(value as string | Date).toLocaleDateString("en-GB") : "-";

    const heading = `${model.title} ${model.kind === "RECEIPT" ? "Receipt" : "Invoice"}`;
    const sealText =
      model.sealText ?? (model.kind === "RECEIPT" || model.status === "PAID" ? "EDR PAID" : "EDR");
    const showCategory = Boolean(model.categoryHeader);

    const ops: string[] = [];

    // ── Header ────────────────────────────────────────────────────────────
    ops.push(lineOp(36, 806, 559, 806, PdfColor.teal, 2.4));
    ops.push(textOp("ETHIO-DJIBOUTI RAILWAY S.C.", 36, 790, 8.5, "F2", PdfColor.gray));
    const titleSize = heading.length > 34 ? 18 : 22;
    ops.push(textOp(heading, 36, 762, titleSize, "F2", PdfColor.dark));

    ops.push(textOpRight("DOCUMENT NO.", 559, 792, 7.5, "F2", PdfColor.gray));
    ops.push(textOpRight(model.documentNumber, 559, 776, 12, "F2", PdfColor.dark));
    ops.push(textOpRight(`Issued  ${date(model.issuedAt)}`, 559, 762, 8.5, "F1", PdfColor.gray));
    ops.push(
      textOpRight(
        `Status  ${model.status}`,
        559,
        748,
        8.5,
        "F1",
        model.status === "PAID" ? PdfColor.teal : PdfColor.gray,
      ),
    );
    ops.push(lineOp(36, 736, 470, 736, PdfColor.line, 1));

    // ── Seal ──────────────────────────────────────────────────────────────
    ops.push(sealOp(516, 706, 27, sealText.split(/\s+/), PdfColor.teal));

    // ── Summary grid (two columns) ────────────────────────────────────────
    let y = 700;
    const colX = [36, 300];
    const colW = 250;
    model.summary.slice(0, 16).forEach((row, i) => {
      const x = colX[i % 2];
      if (i % 2 === 0 && i > 0) y -= 27;
      ops.push(textOp((row.label ?? "").toUpperCase(), x, y, 7, "F1", PdfColor.gray));
      ops.push(textOp(this.clip(row.value ?? "-", 44), x, y - 11, 9, "F2", PdfColor.dark));
      ops.push(lineOp(x, y - 15, x + colW, y - 15, PdfColor.line, 0.6));
    });
    y -= 34;

    // ── Line-item table ───────────────────────────────────────────────────
    const qtyR = 402;
    const rateR = 486;
    const amtR = 555;
    ops.push(rectOp(36, y - 18, 523, 18, PdfColor.shade, PdfColor.line, 0.7));
    ops.push(textOp("DESCRIPTION", 40, y - 13, 8, "F2", PdfColor.gray));
    if (showCategory) {
      ops.push(textOp((model.categoryHeader ?? "").toUpperCase(), 250, y - 13, 8, "F2", PdfColor.gray));
    }
    ops.push(textOpRight("QTY", qtyR, y - 13, 8, "F2", PdfColor.gray));
    ops.push(textOpRight("RATE", rateR, y - 13, 8, "F2", PdfColor.gray));
    ops.push(textOpRight("AMOUNT", amtR, y - 13, 8, "F2", PdfColor.gray));
    y -= 18;

    const descChars = showCategory ? 44 : 66;
    for (const item of model.lines) {
      if (y < 190) break; // leave room for totals + footer
      const descLines = wrapText(item.description ?? "-", descChars).slice(0, 2);
      const rowH = Math.max(18, descLines.length * 10 + 8);
      ops.push(rectOp(36, y - rowH, 523, rowH, "1 1 1", PdfColor.line, 0.6));
      descLines.forEach((line, k) => {
        ops.push(textOp(line, 40, y - 12 - k * 10, 8, "F1", PdfColor.dark));
      });
      if (showCategory) {
        ops.push(textOp(this.clip((item.category ?? "").replace(/_/g, " "), 18), 250, y - 12, 8, "F1", PdfColor.dark));
      }
      ops.push(textOpRight(String(item.quantity ?? 0), qtyR, y - 12, 8, "F1", PdfColor.dark));
      ops.push(textOpRight(money(item.unitRate, item.currency), rateR, y - 12, 8, "F1", PdfColor.dark));
      ops.push(textOpRight(money(item.amount, item.currency), amtR, y - 12, 8, "F1", PdfColor.dark));
      y -= rowH;
    }

    // ── Totals ────────────────────────────────────────────────────────────
    let ty = y - 16;
    for (const total of model.totals) {
      if (ty < 88) break;
      if (total.grand) {
        ops.push(lineOp(315, ty + 5, 559, ty + 5, PdfColor.dark, 0.9));
        ops.push(textOp(total.label, 320, ty - 9, 11, "F2", PdfColor.dark));
        ops.push(textOpRight(money(total.amount), 555, ty - 9, 12, "F2", PdfColor.dark));
        ty -= 24;
      } else {
        ops.push(textOp(total.label, 320, ty - 8, 9.5, "F1", PdfColor.gray));
        ops.push(textOpRight(money(total.amount), 555, ty - 8, 10, "F1", PdfColor.dark));
        ty -= 17;
      }
    }

    // ── Footer ────────────────────────────────────────────────────────────
    ops.push(lineOp(36, 64, 250, 64, PdfColor.dark, 0.8));
    ops.push(textOp("Prepared by EDR finance", 36, 52, 7.5, "F1", PdfColor.gray));
    ops.push(lineOp(340, 64, 559, 64, PdfColor.dark, 0.8));
    ops.push(textOp("Authorized seal / signature", 340, 52, 7.5, "F1", PdfColor.gray));

    return assembleSinglePagePdf(ops);
  }

  /** Truncate to `max` chars with an ellipsis. */
  private clip(value: string, max: number): string {
    const text = String(value ?? "");
    return text.length > max ? `${text.slice(0, max - 3)}...` : text;
  }

  buildHtml(model: InvoiceDocumentModel): string {
    const esc = (value: unknown) =>
      String(value ?? "-")
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
    const money = (amount: unknown, currency = model.currency) =>
      `${Number(amount ?? 0).toLocaleString()} ${currency === "ETB" ? "Birr (ETB)" : currency}`;
    const date = (value: unknown) =>
      value ? new Date(value as string | Date).toLocaleDateString("en-GB") : "-";

    const showCategory = Boolean(model.categoryHeader);
    const sealText =
      model.sealText ?? (model.kind === "RECEIPT" || model.status === "PAID" ? "EDR PAID" : "EDR");

    const summaryRows = model.summary
      .map((row) => `<div><span>${esc(row.label)}</span>${esc(row.value)}</div>`)
      .join("");

    const itemRows = model.lines
      .map(
        (item) => `<tr>
              <td>${esc(item.description)}</td>
              ${showCategory ? `<td>${esc((item.category ?? "").replace(/_/g, " "))}</td>` : ""}
              <td class="num">${esc(item.quantity ?? 0)}</td>
              <td class="num">${esc(money(item.unitRate, item.currency ?? model.currency))}</td>
              <td class="num">${esc(money(item.amount, item.currency ?? model.currency))}</td>
            </tr>`,
      )
      .join("");

    const totalRows = model.totals
      .map(
        (total) =>
          `<div class="total-row${total.grand ? " grand" : ""}"><span>${esc(total.label)}</span><strong>${esc(money(total.amount))}</strong></div>`,
      )
      .join("");

    return `<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>${esc(model.title)} ${model.kind === "RECEIPT" ? "Receipt" : "Invoice"}</title>
  <style>
    body { font-family: Arial, sans-serif; color: #0f172a; margin: 0; }
    .doc { padding: 16px 8px; position: relative; }
    .top { display: flex; justify-content: space-between; gap: 24px; border-bottom: 3px solid #0f766e; padding-bottom: 16px; }
    .brand { font-size: 13px; color: #475569; text-transform: uppercase; letter-spacing: .08em; }
    h1 { margin: 8px 0 0; font-size: 30px; }
    .meta { text-align: right; font-size: 12px; color: #475569; }
    .meta strong { display: block; color: #0f172a; font-size: 17px; margin-top: 5px; }
    .seal { position: absolute; right: 28px; top: 118px; width: 116px; height: 116px; border: 4px double #0f766e; border-radius: 999px; color: #0f766e; display: flex; align-items: center; justify-content: center; text-align: center; font-weight: 800; font-size: 18px; transform: rotate(-14deg); opacity: .82; }
    .summary { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 28px; margin: 24px 150px 16px 0; font-size: 13px; }
    .summary div { border-bottom: 1px solid #e2e8f0; padding: 7px 0; }
    .summary span { color: #64748b; display: block; font-size: 11px; margin-bottom: 3px; }
    table { width: 100%; border-collapse: collapse; margin-top: 18px; }
    th { text-align: left; background: #f8fafc; color: #475569; }
    th, td { border: 1px solid #cbd5e1; padding: 9px 10px; font-size: 12px; }
    td.num, th.num { text-align: right; }
    .totals { margin-left: auto; width: 330px; margin-top: 18px; }
    .total-row { display: flex; justify-content: space-between; border-bottom: 1px solid #e2e8f0; padding: 8px 0; font-size: 13px; }
    .grand { font-size: 16px; font-weight: 800; }
    .footer { margin-top: 34px; display: grid; grid-template-columns: 1fr 1fr; gap: 28px; }
    .line { border-top: 1px solid #334155; padding-top: 8px; font-size: 12px; color: #475569; }
  </style>
</head>
<body>
  <div class="doc">
    <div class="top">
      <div>
        <div class="brand">Ethio-Djibouti Railway S.C.</div>
        <h1>${esc(model.title)} ${model.kind === "RECEIPT" ? "Receipt" : "Invoice"}</h1>
      </div>
      <div class="meta">
        Document no.
        <strong>${esc(model.documentNumber)}</strong>
        Issued: ${esc(date(model.issuedAt))}
      </div>
    </div>
    <div class="seal">${esc(sealText)}</div>
    <div class="summary">${summaryRows}</div>
    <table>
      <thead>
        <tr>
          <th>Description</th>
          ${showCategory ? `<th>${esc(model.categoryHeader)}</th>` : ""}
          <th class="num">Qty</th>
          <th class="num">Rate</th>
          <th class="num">Amount</th>
        </tr>
      </thead>
      <tbody>
        ${itemRows}
      </tbody>
    </table>
    <div class="totals">${totalRows}</div>
    <div class="footer">
      <div class="line">Prepared by EDR finance</div>
      <div class="line">Authorized seal / signature</div>
    </div>
  </div>
</body>
</html>`;
  }

  safeFilename(value: string): string {
    return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
  }
}
