import jsPDF from "jspdf";
import autoTable, { RowInput } from "jspdf-autotable";

type ReportRow = {
  id: string;
  category: string;
  accountType: string;
  parentId?: string | null;
  parentCategory?: string | null;
  budget: number;
  annualBudget?: number;
  actual: number;
  variance: number;
  pctOfBudget: number;
  comparisonActual: number;
  comparisonBudget: number;
  comparisonVariance: number;
  comparisonPctOfBudget: number;
  cmpDelta: number;
};

type Association = { id?: string; name: string; logo_url?: string | null };

// Plain numeric formatting (parentheses for negatives) — matches QuickBooks/management-report style.
const fmtNum = (n: number) => {
  if (n === 0) return "0.00";
  const abs = Math.abs(n).toLocaleString("en-US", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
  return n < 0 ? `(${abs})` : abs;
};

const fmtCurrency = (n: number) => {
  if (n === 0) return "$0.00";
  const abs = "$" + Math.abs(n).toLocaleString("en-US", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
  return n < 0 ? `(${abs})` : abs;
};

const fmtPctOfBudget = (actual: number, budget: number) => {
  if (!budget || !isFinite(actual / budget)) return "--";
  return `${((actual / budget) * 100).toFixed(2)} %`;
};

const fmtVariancePct = (n: number) =>
  isFinite(n) ? `${n >= 0 ? "+" : ""}${n.toFixed(1)}%` : "—";

async function loadImageAsDataURL(url: string): Promise<{ data: string; format: string } | null> {
  try {
    const res = await fetch(url, { mode: "cors" });
    const blob = await res.blob();
    const format = blob.type.includes("png")
      ? "PNG"
      : blob.type.includes("jpeg") || blob.type.includes("jpg")
        ? "JPEG"
        : "PNG";
    const data: string = await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
    return { data, format };
  } catch {
    return null;
  }
}

export async function generateBudgetVsActualPdf(opts: {
  association: Association;
  fiscalYear: number;
  rangeLabel: string;
  comparisonLabel: string | null;
  comparisonRangeLabel: string | null;
  rows: ReportRow[];
  totals: {
    incomeBudget: number;
    incomeActual: number;
    incomeCmp: number;
    incomeCmpBudget: number;
    expenseBudget: number;
    expenseActual: number;
    expenseCmp: number;
    expenseCmpBudget: number;
  };
  comparisonBudgetMonths?: number | null;
}) {
  const {
    association,
    fiscalYear,
    rangeLabel,
    comparisonLabel,
    comparisonRangeLabel,
    rows,
    totals,
  } = opts;
  const showCmp = comparisonLabel !== null;
  const doc = new jsPDF({ unit: "pt", format: "letter", orientation: "landscape" });
  const pageWidth = doc.internal.pageSize.getWidth();
  const pageHeight = doc.internal.pageSize.getHeight();
  const marginX = 40;

  // ---- Header (centered title, logo top-left) ----
  if (association.logo_url) {
    const img = await loadImageAsDataURL(association.logo_url);
    if (img) {
      try {
        doc.addImage(img.data, img.format, marginX, 30, 55, 55, undefined, "FAST");
      } catch {
        /* ignore */
      }
    }
  }

  doc.setFont("helvetica", "normal");
  doc.setFontSize(20);
  doc.setTextColor(0);
  doc.text("Budget vs. Actuals", pageWidth / 2, 55, { align: "center" });
  doc.setFontSize(11);
  doc.text("Accrual basis", pageWidth / 2, 73, { align: "center" });

  // Bold sub-header line
  doc.setFont("helvetica", "bold");
  doc.setFontSize(10);
  doc.text(
    `${association.name} - Association level - FY${fiscalYear} Approved Budget`,
    marginX,
    110,
  );
  doc.setFont("helvetica", "normal");

  let cursorY = 120;

  // ---- Table construction ----
  // Column groups: current period (Actual/Budget/Variance/% of Budget),
  // optional comparison period (Actual/Budget/Variance/% of Budget),
  // and Annual Budget at the end.
  const cmpPctOfBudget = (actual: number, budget: number) => fmtPctOfBudget(actual, budget);

  // Group header row spanning current period block
  const groupHeader: RowInput = [
    { content: "", styles: { fillColor: [255, 255, 255] } },
    {
      content: rangeLabel,
      colSpan: 4,
      styles: { halign: "center", fontStyle: "normal", fillColor: [255, 255, 255], textColor: [80, 80, 80] },
    },
  ];
  if (showCmp) {
    groupHeader.push({
      content: comparisonRangeLabel || comparisonLabel || "Comparison",
      colSpan: 4,
      styles: { halign: "center", fontStyle: "normal", fillColor: [255, 255, 255], textColor: [80, 80, 80] },
    });
  }
  groupHeader.push({
    content: `FY${fiscalYear}`,
    styles: { halign: "center", fontStyle: "normal", fillColor: [255, 255, 255], textColor: [80, 80, 80] },
  });

  const columnHeader: RowInput = [
    { content: "Account", styles: { halign: "left" } },
    { content: "Actual", styles: { halign: "right" } },
    { content: "Budget", styles: { halign: "right" } },
    { content: "Variance", styles: { halign: "right" } },
    { content: "% of Budget", styles: { halign: "right" } },
  ];
  if (showCmp) {
    columnHeader.push({ content: "Actual", styles: { halign: "right" } });
    columnHeader.push({ content: "Budget", styles: { halign: "right" } });
    columnHeader.push({ content: "Variance", styles: { halign: "right" } });
    columnHeader.push({ content: "% of Budget", styles: { halign: "right" } });
  }
  columnHeader.push({ content: "Annual Budget", styles: { halign: "right" } });

  const colCount = columnHeader.length;

  // Build body in grouped sections (Income / Expense) with subtotals.
  const body: RowInput[] = [];

  const sectionLabel = (label: string) => {
    body.push([
      {
        content: label,
        colSpan: colCount,
        styles: { fontStyle: "bold", fillColor: [255, 255, 255], cellPadding: { top: 4, bottom: 2, left: 4, right: 4 } },
      },
    ]);
  };

  const lineRow = (r: ReportRow, annualBudget: number) => {
    const variance = r.actual - r.budget;
    const row: RowInput = [
      { content: r.category, styles: { halign: "left", cellPadding: { top: 2, bottom: 2, left: 18, right: 4 } } },
      { content: fmtNum(r.actual), styles: { halign: "right" } },
      { content: fmtNum(r.budget), styles: { halign: "right" } },
      { content: fmtNum(variance), styles: { halign: "right" } },
      { content: fmtPctOfBudget(r.actual, r.budget), styles: { halign: "right" } },
    ];
    if (showCmp) {
      const cmpVar = r.comparisonVariance;
      row.push({ content: fmtNum(r.comparisonActual), styles: { halign: "right" } });
      row.push({ content: fmtNum(r.comparisonBudget), styles: { halign: "right" } });
      row.push({ content: fmtNum(cmpVar), styles: { halign: "right" } });
      row.push({ content: cmpPctOfBudget(r.comparisonActual, r.comparisonBudget), styles: { halign: "right" } });
    }
    row.push({ content: fmtNum(annualBudget), styles: { halign: "right" } });
    body.push(row);
  };

  const totalRow = (
    label: string,
    sums: { actual: number; budget: number; cmpActual: number; cmpBudget: number; annual: number },
  ) => {
    const variance = sums.actual - sums.budget;
    const row: RowInput = [
      {
        content: label,
        styles: { halign: "left", fontStyle: "bold", cellPadding: { top: 3, bottom: 3, left: 10, right: 4 } },
      },
      { content: fmtCurrency(sums.actual), styles: { halign: "right", fontStyle: "bold" } },
      { content: fmtCurrency(sums.budget), styles: { halign: "right", fontStyle: "bold" } },
      { content: fmtCurrency(variance), styles: { halign: "right", fontStyle: "bold" } },
      { content: fmtPctOfBudget(sums.actual, sums.budget), styles: { halign: "right", fontStyle: "bold" } },
    ];
    if (showCmp) {
      const cmpVar = sums.cmpActual - sums.cmpBudget;
      row.push({ content: fmtCurrency(sums.cmpActual), styles: { halign: "right", fontStyle: "bold" } });
      row.push({ content: fmtCurrency(sums.cmpBudget), styles: { halign: "right", fontStyle: "bold" } });
      row.push({ content: fmtCurrency(cmpVar), styles: { halign: "right", fontStyle: "bold" } });
      row.push({ content: fmtPctOfBudget(sums.cmpActual, sums.cmpBudget), styles: { halign: "right", fontStyle: "bold" } });
    }
    row.push({ content: fmtCurrency(sums.annual), styles: { halign: "right", fontStyle: "bold" } });
    body.push(row);
  };

  const sortRows = (list: ReportRow[]) =>
    [...list].sort((a, b) => a.category.localeCompare(b.category, undefined, { numeric: true }));

  const incomeRows = sortRows(rows.filter((r) => r.accountType === "income"));
  const expenseRows = sortRows(rows.filter((r) => r.accountType !== "income"));

  const annualOf = (r: ReportRow) => Number(r.annualBudget ?? r.budget);

  // Group rows by parent category, render each parent as a sub-header with a
  // subtotal row, and orphan rows (no parent) under an "Other" group.
  const renderGrouped = (sectionTitle: string, sectionRows: ReportRow[]) => {
    sectionLabel(sectionTitle);
    const groups = new Map<string, ReportRow[]>();
    for (const r of sectionRows) {
      const key = r.parentCategory || "__orphan__";
      const list = groups.get(key) || [];
      list.push(r);
      groups.set(key, list);
    }
    // Sort parent groups alphabetically; orphans at the end.
    const orderedKeys = Array.from(groups.keys()).sort((a, b) => {
      if (a === "__orphan__") return 1;
      if (b === "__orphan__") return -1;
      return a.localeCompare(b, undefined, { numeric: true });
    });
    for (const key of orderedKeys) {
      const list = groups.get(key)!;
      const isOrphan = key === "__orphan__";
      if (!isOrphan) {
        body.push([
          {
            content: key,
            colSpan: colCount,
            styles: {
              fontStyle: "bold",
              fillColor: [248, 248, 248],
              cellPadding: { top: 3, bottom: 3, left: 10, right: 4 },
            },
          },
        ]);
      }
      list.forEach((r) => lineRow(r, annualOf(r)));
      if (!isOrphan && list.length > 1) {
        const subActual = list.reduce((s, r) => s + r.actual, 0);
        const subBudget = list.reduce((s, r) => s + r.budget, 0);
        const subCmp = list.reduce((s, r) => s + r.comparisonActual, 0);
        const subCmpBudget = list.reduce((s, r) => s + r.comparisonBudget, 0);
        const subAnnual = list.reduce((s, r) => s + annualOf(r), 0);
        totalRow(`Total ${key}`, {
          actual: subActual,
          budget: subBudget,
          cmpActual: subCmp,
          cmpBudget: subCmpBudget,
          annual: subAnnual,
        });
      }
    }
  };

  // Income section
  renderGrouped("Income", incomeRows);
  totalRow("Total for Income", {
    actual: totals.incomeActual,
    budget: totals.incomeBudget,
    cmpActual: totals.incomeCmp,
    cmpBudget: totals.incomeCmpBudget,
    annual: incomeRows.reduce((s, r) => s + annualOf(r), 0),
  });

  // Spacer
  body.push([{ content: " ", colSpan: colCount, styles: { fillColor: [255, 255, 255], cellPadding: 2 } }]);

  // Expense section
  renderGrouped("Expense", expenseRows);
  totalRow("Total for Expense", {
    actual: totals.expenseActual,
    budget: totals.expenseBudget,
    cmpActual: totals.expenseCmp,
    cmpBudget: totals.expenseCmpBudget,
    annual: expenseRows.reduce((s, r) => s + annualOf(r), 0),
  });

  // Net income
  body.push([{ content: " ", colSpan: colCount, styles: { fillColor: [255, 255, 255], cellPadding: 2 } }]);
  totalRow("Net Income", {
    actual: totals.incomeActual - totals.expenseActual,
    budget: totals.incomeBudget - totals.expenseBudget,
    cmpActual: totals.incomeCmp - totals.expenseCmp,
    cmpBudget: totals.incomeCmpBudget - totals.expenseCmpBudget,
    annual:
      incomeRows.reduce((s, r) => s + annualOf(r), 0) -
      expenseRows.reduce((s, r) => s + annualOf(r), 0),
  });

  autoTable(doc, {
    startY: cursorY,
    head: [groupHeader, columnHeader],
    body,
    theme: "plain",
    styles: {
      font: "helvetica",
      fontSize: 8.5,
      cellPadding: { top: 2, bottom: 2, left: 4, right: 4 },
      lineColor: [220, 220, 220],
      lineWidth: 0,
      textColor: [0, 0, 0],
    },
    headStyles: {
      fillColor: [245, 245, 245],
      textColor: [0, 0, 0],
      fontStyle: "bold",
      lineColor: [180, 180, 180],
      lineWidth: 0.25,
    },
    columnStyles: {
      0: { cellWidth: showCmp ? 200 : 260 },
    },
    margin: { left: marginX, right: marginX },
    didParseCell: (data) => {
      // Light horizontal divider under each row in the body
      if (data.section === "body") {
        data.cell.styles.lineWidth = 0;
      }
    },
    didDrawCell: (data) => {
      if (data.section === "head" && data.row.index === 1) {
        // bottom border under column header row
        const { x, y, width, height } = data.cell;
        doc.setDrawColor(120);
        doc.setLineWidth(0.5);
        doc.line(x, y + height, x + width, y + height);
      }
    },
  });

  // Footer
  const pageCount = doc.getNumberOfPages();
  for (let i = 1; i <= pageCount; i++) {
    doc.setPage(i);
    doc.setFontSize(8);
    doc.setTextColor(120);
    doc.text(
      `Generated ${new Date().toLocaleString("en-US", { timeZone: "America/New_York" })}`,
      marginX,
      pageHeight - 20,
    );
    doc.text(`Page ${i} of ${pageCount}`, pageWidth - marginX, pageHeight - 20, { align: "right" });
    doc.setTextColor(0);
  }

  doc.save(
    `${association.name.replace(/[^a-z0-9]+/gi, "_")}_Budget_vs_Actual_FY${fiscalYear}.pdf`,
  );
}
