import jsPDF from "jspdf";
import { MICR_CHECK_FONT_BASE64 } from "@/assets/fonts/micrCheckFont";

/**
 * Standard business check format: 8.5" x 11" page with one check on top
 * (top 3.5") and two stubs below (middle 3.5" + bottom 4"). Coordinates in inches.
 *
 * The layout supports per-field positioning so users can move/scale every
 * label and value to fit their exact check stock or completely blank paper.
 */

export interface CheckLineItem {
  /** Invoice / reference number for the line */
  invoice_number?: string | null;
  /** Date of the bill/invoice */
  date?: string | null;
  /** Short description (vendor memo, expense category, etc.) */
  description?: string | null;
  /** Line amount in dollars */
  amount: number;
}

export interface CheckData {
  check_number?: string | null;
  check_date?: string | null;
  payee: string;
  /** Optional payee mailing address (multi-line). Printed under the payee name. */
  payee_address?: string | null;
  amount: number;
  memo?: string | null;
  /** Optional itemized breakdown shown on the stub (e.g. invoices combined into one check) */
  line_items?: CheckLineItem[] | null;
  bank_account_name?: string | null;
  bank_routing_number?: string | null;
  bank_account_number?: string | null;
  association_name?: string | null;
  /** Per-association layout override (loaded from `check_layouts`) */
  layout?: CheckLayout | null;
}

export type CheckFieldKey =
  | "payer"
  | "logo"
  | "date"
  | "check_number"
  | "payee_label"
  | "payee"
  | "payee_address"
  | "amount_box_label"
  | "amount_box"
  | "amount_words"
  | "memo_label"
  | "memo"
  | "signature_line"
  | "signature_image"
  | "signature_label"
  | "footer"
  | "micr";

export interface CheckFieldPosition {
  visible?: boolean;
  /** X offset in inches from the check's left edge */
  x?: number;
  /** Y offset in inches from the check's top edge */
  y?: number;
  font_size?: number;
  /** Optional override for the printed label (e.g. "Pay to the order of") */
  label?: string;
  /** Optional width override (inches) — used by logo and signature-line */
  width?: number;
  /** Optional height override (inches) — used by logo and signature image */
  height?: number;
}

export interface CheckLayout {
  check_position?: "top" | "middle" | "bottom" | null;
  offset_x?: number | null;
  offset_y?: number | null;
  show_payer_block?: boolean | null;
  show_logo?: boolean | null;
  payer_name?: string | null;
  payer_address?: string | null;
  show_signature_line?: boolean | null;
  signature_image_url?: string | null;
  signature_label?: string | null;
  memo_prefix?: string | null;
  footer_text?: string | null;
  /** New: master toggle to print "Payee", "Memo", "Dollars" labels */
  show_field_labels?: boolean | null;
  /** New: jsPDF font family for all check text. Default "helvetica". */
  font_family?: string | null;
  /** New: per-field positioning, font sizes, and labels. */
  field_positions?: Partial<Record<CheckFieldKey, CheckFieldPosition>> | null;
  /** New: optional logo image URL printed on the check. */
  logo_url?: string | null;
  /** Spaces between MICR check# and routing segments (default 3). */
  micr_gap_1?: number | null;
  /** Spaces between MICR routing and account segments (default 3). */
  micr_gap_2?: number | null;
}

/* ------------------------------------------------------------------ */
/* Defaults                                                            */
/* ------------------------------------------------------------------ */

/**
 * Default position for every check field (relative to the check band's
 * top-left corner, in inches). Override these per-field via `field_positions`.
 */
export const DEFAULT_FIELD_POSITIONS: Record<CheckFieldKey, CheckFieldPosition> = {
  payer:           { visible: true,  x: 0.5, y: 0.45, font_size: 10, label: "" },
  logo:            { visible: true,  x: 0.4, y: 0.2,  width: 1.0,  height: 0.6 },
  date:            { visible: true,  x: 6.6, y: 0.85, font_size: 10, label: "Date" },
  check_number:    { visible: true,  x: 7.4, y: 0.55, font_size: 11, label: "" },
  payee_label:     { visible: true,  x: 0.5, y: 1.30, font_size: 9,  label: "Pay to the order of" },
  payee:           { visible: true,  x: 1.85,y: 1.30, font_size: 11, label: "" },
  payee_address:   { visible: true,  x: 1.85,y: 1.47, font_size: 9,  label: "" },
  amount_box_label:{ visible: true,  x: 6.55,y: 1.10, font_size: 9,  label: "$" },
  amount_box:      { visible: true,  x: 6.7, y: 1.30, font_size: 11, label: "" },
  amount_words:    { visible: true,  x: 0.5, y: 1.75, font_size: 10, label: "" },
  memo_label:      { visible: true,  x: 0.4, y: 2.85, font_size: 9,  label: "Memo" },
  memo:            { visible: true,  x: 0.85,y: 2.85, font_size: 9,  label: "" },
  signature_line:  { visible: true,  x: 5.0, y: 2.85, width: 2.7,   label: "" },
  signature_image: { visible: true,  x: 5.1, y: 2.30, width: 2.0833, height: 0.7917 },
  signature_label: { visible: true,  x: 5.0, y: 3.00, font_size: 8,  label: "Authorized Signature" },
  footer:          { visible: true,  x: 0.5, y: 3.05, font_size: 8,  label: "" },
  micr:            { visible: true,  x: 0.6, y: 3.25, font_size: 12, label: "" },
};

export const FIELD_LABELS: Record<CheckFieldKey, string> = {
  payer: "Payer Name & Address",
  logo: "Logo",
  date: "Date",
  check_number: "Check Number",
  payee_label: "“Pay to the order of” Label",
  payee: "Payee Name",
  payee_address: "Payee Address",
  amount_box_label: "Amount Box Label ($)",
  amount_box: "Numeric Amount",
  amount_words: "Written Amount + “Dollars”",
  memo_label: "Memo Label",
  memo: "Memo Text",
  signature_line: "Signature Line",
  signature_image: "Signature Image",
  signature_label: "Signature Label",
  footer: "Footer Text",
  micr: "MICR Line",
};

export const AVAILABLE_FONTS = [
  { value: "helvetica", label: "Helvetica (Sans-serif)" },
  { value: "times",     label: "Times (Serif)" },
  { value: "courier",   label: "Courier (Mono)" },
] as const;

function getPos(layout: CheckLayout | undefined | null, key: CheckFieldKey): CheckFieldPosition {
  const def = DEFAULT_FIELD_POSITIONS[key];
  const override = layout?.field_positions?.[key] || {};
  return {
    visible: override.visible ?? def.visible,
    x: override.x ?? def.x,
    y: override.y ?? def.y,
    font_size: override.font_size ?? def.font_size,
    label: override.label ?? def.label,
    width: override.width ?? def.width,
    height: override.height ?? def.height,
  };
}

/* ------------------------------------------------------------------ */
/* Helpers                                                             */
/* ------------------------------------------------------------------ */

function resolveCheckYBase(layout?: CheckLayout | null): number {
  switch (layout?.check_position) {
    case "middle": return 3.6;
    case "bottom": return 7.2;
    case "top":
    default: return 0;
  }
}

async function fetchImageAsDataUrl(url: string): Promise<string | null> {
  try {
    const res = await fetch(url);
    if (!res.ok) return null;
    const blob = await res.blob();
    return await new Promise<string>((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result as string);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  } catch {
    return null;
  }
}

const MICR_TRANSIT = "A";
const MICR_ON_US = "B";

function registerMicrFont(doc: jsPDF) {
  doc.addFileToVFS("MICRCHECK.ttf", MICR_CHECK_FONT_BASE64);
  doc.addFont("MICRCHECK.ttf", "MICRCHECK", "normal");
}

export function formatMicrLine(routing: string, account: string, checkNumber: string, gap1 = 3, gap2 = 3): string {
  const r = (routing || "").replace(/\D/g, "");
  const a = (account || "").replace(/\D/g, "");
  const c = (checkNumber || "").replace(/\D/g, "");
  // Pad check number with leading zeros to 9 digits
  const paddedCheck = c.padStart(9, "0");
  const g1 = " ".repeat(Math.max(0, Math.round(gap1)));
  const g2 = " ".repeat(Math.max(0, Math.round(gap2)));
  // Order: check number (on-us), gap1, transit/routing, gap2, account
  return `${MICR_ON_US}${paddedCheck}${MICR_ON_US}${g1}${MICR_TRANSIT}${r}${MICR_TRANSIT}${g2}${a}${MICR_ON_US}`;
}

export function amountToWords(amount: number): string {
  if (!isFinite(amount)) return "";
  const dollars = Math.floor(Math.abs(amount));
  const cents = Math.round((Math.abs(amount) - dollars) * 100);
  const ones = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"];
  const tens = ["","","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety"];
  const under1000 = (n: number): string => {
    if (n === 0) return "";
    if (n < 20) return ones[n];
    if (n < 100) {
      const t = Math.floor(n / 10);
      const o = n % 10;
      return tens[t] + (o ? "-" + ones[o] : "");
    }
    const h = Math.floor(n / 100);
    const r = n % 100;
    return ones[h] + " Hundred" + (r ? " " + under1000(r) : "");
  };
  const convert = (n: number): string => {
    if (n === 0) return "Zero";
    const billion = Math.floor(n / 1_000_000_000);
    const million = Math.floor((n % 1_000_000_000) / 1_000_000);
    const thousand = Math.floor((n % 1_000_000) / 1000);
    const rest = n % 1000;
    const parts: string[] = [];
    if (billion) parts.push(under1000(billion) + " Billion");
    if (million) parts.push(under1000(million) + " Million");
    if (thousand) parts.push(under1000(thousand) + " Thousand");
    if (rest) parts.push(under1000(rest));
    return parts.join(" ");
  };
  return `${convert(dollars)} and ${cents.toString().padStart(2, "0")}/100`;
}

const formatDate = (iso?: string | null): string => {
  if (!iso) return "";
  const d = new Date(iso + (iso.length === 10 ? "T00:00:00" : ""));
  if (isNaN(d.getTime())) return "";
  return d.toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" });
};

const formatMoney = (n: number) =>
  n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });

/* ------------------------------------------------------------------ */
/* Drawing                                                             */
/* ------------------------------------------------------------------ */

function drawCheckSection(
  doc: jsPDF,
  c: CheckData,
  yOffset: number,
  includeMicr: boolean,
  signatureDataUrl: string | null,
  logoDataUrl: string | null
) {
  const layout = c.layout || {};
  const dx = Number(layout.offset_x) || 0;
  const dy = Number(layout.offset_y) || 0;
  const showLabels = !!layout.show_field_labels;
  const fontFamily = (layout.font_family && ["helvetica", "times", "courier"].includes(layout.font_family))
    ? layout.font_family : "helvetica";

  const px = (key: CheckFieldKey) => {
    const p = getPos(layout, key);
    return { ...p, X: (p.x ?? 0) + dx, Y: yOffset + (p.y ?? 0) + dy };
  };
  const drawLabel = (p: ReturnType<typeof px>, suffix = " ") => {
    if (showLabels && p.label) {
      doc.setFont(fontFamily, "normal").setFontSize(Math.max(7, (p.font_size || 10) - 2));
      doc.text(p.label + suffix, p.X, p.Y - 0.14);
    }
  };

  // --- Logo (image) ---
  if (layout.show_logo !== false && logoDataUrl) {
    const p = px("logo");
    if (p.visible !== false) {
      try {
        doc.addImage(logoDataUrl, "PNG", p.X, p.Y, p.width || 1.0, p.height || 0.6);
      } catch { /* ignore unsupported image */ }
    }
  }

  // --- Payer block ---
  if (layout.show_payer_block !== false) {
    const p = px("payer");
    if (p.visible !== false) {
      const name = layout.payer_name || c.association_name || "";
      doc.setFont(fontFamily, "bold").setFontSize(p.font_size || 10);
      if (name) doc.text(name, p.X, p.Y);
      if (layout.payer_address) {
        doc.setFont(fontFamily, "normal").setFontSize(Math.max(7, (p.font_size || 10) - 2));
        const lines = layout.payer_address.split(/\r?\n/).slice(0, 3);
        lines.forEach((ln, i) => doc.text(ln, p.X, p.Y + 0.17 + i * 0.13));
      }
    }
  }

  // --- Date ---
  {
    const p = px("date");
    if (p.visible !== false) {
      drawLabel(p, ": ");
      doc.setFont(fontFamily, "normal").setFontSize(p.font_size || 10);
      doc.text(formatDate(c.check_date), p.X + (showLabels ? 0.5 : 0), p.Y);
    }
  }

  // --- Check number ---
  if (c.check_number) {
    const p = px("check_number");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "bold").setFontSize(p.font_size || 11);
      doc.text(String(c.check_number), p.X, p.Y);
    }
  }

  // --- Payee label + name ---
  {
    const lp = px("payee_label");
    if (showLabels && lp.visible !== false && lp.label) {
      doc.setFont(fontFamily, "normal").setFontSize(lp.font_size || 9);
      doc.text(lp.label, lp.X, lp.Y);
    }
    const p = px("payee");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "normal").setFontSize(p.font_size || 11);
      doc.text(c.payee || "", p.X, p.Y);
      // Underline beneath the entire "Pay to the order of" payee field, extending all the way to the amount box
      const amtBoxPos = px("amount_box");
      const lineEnd = (amtBoxPos.visible !== false ? amtBoxPos.X - 0.15 : 7.5 + dx);
      const lineStart = p.X;
      if (lineStart < lineEnd) {
        doc.setLineWidth(0.005);
        doc.line(lineStart, p.Y + 0.03, lineEnd, p.Y + 0.03);
      }
    }
  }

  // --- Payee address (independently positioned) ---
  if (c.payee_address) {
    const ap = px("payee_address");
    if (ap.visible !== false) {
      const addrLines = c.payee_address.split(/\r?\n/).map(l => l.trim()).filter(Boolean).slice(0, 4);
      if (addrLines.length > 0) {
        doc.setFont(fontFamily, "normal").setFontSize(ap.font_size || 9);
        const lh = Math.max(0.11, ((ap.font_size || 9) / 72) * 1.2);
        addrLines.forEach((ln, i) => doc.text(ln, ap.X, ap.Y + i * lh));
      }
    }
  }

  // --- Numeric amount box ---
  {
    const lp = px("amount_box_label");
    if (lp.visible !== false && lp.label) {
      doc.setFont(fontFamily, "bold").setFontSize(lp.font_size || 11);
      doc.text(lp.label, lp.X, lp.Y + 0.2);
    }
    const p = px("amount_box");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "bold").setFontSize(p.font_size || 11);
      doc.text(formatMoney(c.amount), p.X, p.Y);
    }
  }

  // --- Written amount + "Dollars" (Dollars anchored at end of line) ---
  {
    const p = px("amount_words");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "normal").setFontSize(p.font_size || 10);
      const words = amountToWords(c.amount);
      const dollarsLabel = "Dollars";
      doc.text(words, p.X, p.Y);
      const dollarsWidth = doc.getTextWidth(dollarsLabel);
      const lineEnd = 7.5 + dx;
      // Place "Dollars" right-aligned at the end of the line
      const dollarsX = lineEnd - dollarsWidth;
      doc.text(dollarsLabel, dollarsX, p.Y);
      // Draw the underline beneath the entire written amount field, all the way to "Dollars"
      const lineStart = p.X;
      const lineStop = dollarsX - 0.1;
      if (lineStart < lineStop) {
        doc.setLineWidth(0.005);
        doc.line(lineStart, p.Y + 0.03, lineStop, p.Y + 0.03);
      }
    }
  }

  // --- Memo ---
  const memoText = [layout.memo_prefix, c.memo].filter(Boolean).join(" ");
  if (memoText) {
    const lp = px("memo_label");
    if (showLabels && lp.visible !== false && lp.label) {
      doc.setFont(fontFamily, "bold").setFontSize(lp.font_size || 9);
      doc.text(lp.label + ":", lp.X, lp.Y);
    }
    const p = px("memo");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "italic").setFontSize(p.font_size || 9);
      doc.text(memoText, p.X, p.Y);
    }
  }

  // --- Signature line + label ---
  if (layout.show_signature_line !== false) {
    const p = px("signature_line");
    if (p.visible !== false) {
      doc.setLineWidth(0.005);
      doc.line(p.X, p.Y, p.X + (p.width || 2.7), p.Y);
    }
    const lp = px("signature_label");
    if (lp.visible !== false) {
      const labelText = layout.signature_label || lp.label || "";
      if (labelText) {
        doc.setFont(fontFamily, "normal").setFontSize(lp.font_size || 8);
        doc.text(labelText, lp.X, lp.Y);
      }
    }
  }

  // --- Signature image (independent positioning, unconstrained size) ---
  if (signatureDataUrl) {
    const sp = px("signature_image");
    if (sp.visible !== false) {
      try {
        doc.addImage(
          signatureDataUrl,
          "PNG",
          sp.X,
          sp.Y,
          sp.width || 2.5,
          sp.height || 0.55
        );
      } catch { /* ignore */ }
    }
  }

  // --- Footer ---
  if (layout.footer_text) {
    const p = px("footer");
    if (p.visible !== false) {
      doc.setFont(fontFamily, "normal").setFontSize(p.font_size || 8);
      doc.text(layout.footer_text, p.X, p.Y);
    }
  }

  // --- MICR line ---
  if (includeMicr && c.bank_routing_number && c.bank_account_number) {
    const p = px("micr");
    if (p.visible !== false) {
      const micr = formatMicrLine(c.bank_routing_number, c.bank_account_number, c.check_number || "", layout.micr_gap_1 ?? 3, layout.micr_gap_2 ?? 3);
      doc.setFont("MICRCHECK", "normal").setFontSize(p.font_size || 12);
      doc.text(micr, p.X, p.Y);
    }
  }
}

function drawStub(doc: jsPDF, c: CheckData, yOffset: number, label: string) {
  doc.setFont("helvetica", "bold").setFontSize(9);
  doc.text(label, 0.5, yOffset + 0.3);

  doc.setFont("helvetica", "normal").setFontSize(9);
  const rows: Array<[string, string]> = [
    ["Check #:", c.check_number ? String(c.check_number) : "—"],
    ["Date:", formatDate(c.check_date)],
    ["Payee:", c.payee],
    ["Amount:", `$${formatMoney(c.amount)}`],
  ];
  if (c.memo) rows.push(["Memo:", c.memo]);
  if (c.bank_account_name) rows.push(["Account:", c.bank_account_name]);

  rows.forEach(([k, v], i) => {
    const y = yOffset + 0.6 + i * 0.22;
    doc.setFont("helvetica", "bold");
    doc.text(k, 0.5, y);
    doc.setFont("helvetica", "normal");
    doc.text(v, 1.6, y);
  });

  // Itemized invoices (when multiple bills are combined into one check)
  const items = (c.line_items || []).filter((li) => li);
  if (items.length > 0) {
    const tableTop = yOffset + 0.6 + rows.length * 0.22 + 0.18;
    const colDate = 0.5;
    const colInv = 1.2;
    const colDesc = 2.5;
    const colAmt = 7.6; // right-aligned anchor
    const pageRight = 8.0;
    const lineHeight = 0.18;

    doc.setFont("helvetica", "bold").setFontSize(8);
    doc.text("Invoices Paid", colDate, tableTop);

    // Header row
    const headerY = tableTop + 0.18;
    doc.setFontSize(8);
    doc.text("Date", colDate, headerY);
    doc.text("Invoice #", colInv, headerY);
    doc.text("Description", colDesc, headerY);
    doc.text("Amount", colAmt, headerY, { align: "right" });
    doc.setLineWidth(0.005);
    doc.line(colDate, headerY + 0.04, pageRight, headerY + 0.04);

    doc.setFont("helvetica", "normal").setFontSize(8);
    let y = headerY + 0.18;
    const maxY = yOffset + 3.3; // keep within stub band
    let totalShown = 0;
    let printedRows = 0;
    for (const li of items) {
      if (y > maxY - 0.25) {
        doc.text(`+ ${items.length - printedRows} more…`, colDate, y);
        break;
      }
      const dateStr = li.date ? formatDate(li.date) : "";
      const invStr = (li.invoice_number || "").toString();
      const descStr = (li.description || "").toString();
      const amtStr = `$${formatMoney(Number(li.amount) || 0)}`;
      doc.text(dateStr, colDate, y);
      doc.text(truncate(invStr, 14), colInv, y);
      doc.text(truncate(descStr, 50), colDesc, y);
      doc.text(amtStr, colAmt, y, { align: "right" });
      totalShown += Number(li.amount) || 0;
      y += lineHeight;
      printedRows++;
    }
    // Total row
    if (printedRows === items.length) {
      doc.line(colDesc, y - lineHeight + 0.04, pageRight, y - lineHeight + 0.04);
      doc.setFont("helvetica", "bold");
      doc.text("Total", colDesc, y);
      doc.text(`$${formatMoney(totalShown)}`, colAmt, y, { align: "right" });
    }
  }
}

function truncate(s: string, max: number) {
  if (!s) return "";
  return s.length <= max ? s : s.slice(0, max - 1) + "…";
}

/* ------------------------------------------------------------------ */
/* Public API                                                          */
/* ------------------------------------------------------------------ */

export async function generateChecksPdf(
  checks: CheckData[],
  options: { includeMicr?: boolean } = {}
): Promise<Blob> {
  const doc = new jsPDF({ unit: "in", format: "letter" });
  const includeMicr = !!options.includeMicr;
  if (includeMicr) registerMicrFont(doc);

  // Pre-fetch signature & logo images (cache by URL)
  const imgCache: Record<string, string | null> = {};
  for (const c of checks) {
    for (const url of [c.layout?.signature_image_url, c.layout?.logo_url]) {
      if (url && !(url in imgCache)) imgCache[url] = await fetchImageAsDataUrl(url);
    }
  }

  checks.forEach((c, idx) => {
    if (idx > 0) doc.addPage();
    const checkY = resolveCheckYBase(c.layout);
    const sigUrl = c.layout?.signature_image_url || null;
    const logoUrl = c.layout?.logo_url || null;
    drawCheckSection(
      doc,
      c,
      checkY,
      includeMicr,
      sigUrl ? imgCache[sigUrl] : null,
      logoUrl ? imgCache[logoUrl] : null
    );

    const bands = [0, 3.6, 7.1];
    const stubs = bands.filter((y) => y !== checkY);
    if (stubs[0] !== undefined) drawStub(doc, c, stubs[0], "PAYEE COPY");
    if (stubs[1] !== undefined) drawStub(doc, c, stubs[1], "ACCOUNTING COPY");
  });

  return doc.output("blob");
}

export async function downloadChecksPdf(
  checks: CheckData[],
  filename = "checks.pdf",
  options?: { includeMicr?: boolean }
) {
  const blob = await generateChecksPdf(checks, options);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}
