import { describe, it, expect } from "vitest";
import type { CategoryDelta } from "../../shared/types";
import { computeResults, sumLeaves } from "./compareResults";

// Minimal leaf factory. Amounts arrive from COMPARE_DELTA_SQL already in the
// per-type sign convention (expenses = positive ABS, income/transfer = signed).
function leaf(
  type: "expense" | "income" | "transfer",
  mc: number,
  mp: number,
  cc = mc,
  cp = mp,
): CategoryDelta {
  return {
    categoryId: 1,
    categoryName: type,
    categoryColor: "#000",
    previousAmount: mp,
    currentAmount: mc,
    deltaAbs: mc - mp,
    deltaPct: mp !== 0 ? ((mc - mp) / mp) * 100 : null,
    cumulativePreviousAmount: cp,
    cumulativeCurrentAmount: cc,
    cumulativeDeltaAbs: cc - cp,
    cumulativeDeltaPct: cp !== 0 ? ((cc - cp) / cp) * 100 : null,
    category_type: type,
    is_parent: false,
  };
}

describe("computeResults — income-statement roll-up (Issue #253)", () => {
  it("resultBefore = revenues − expenses; resultNet adds net transfers", () => {
    const r = computeResults([
      leaf("income", 4350, 4500),
      leaf("expense", 3900, 3700),
      leaf("transfer", 0, 0),
    ]);
    // Operating result: 4350 − 3900 = 450 current; 4500 − 3700 = 800 previous.
    expect(r.resultBefore.monthCurrent).toBe(450);
    expect(r.resultBefore.monthPrevious).toBe(800);
    expect(r.resultBefore.monthDelta).toBe(-350);
    // Balanced transfer (0) → net equals the operating result.
    expect(r.resultNet.monthCurrent).toBe(450);
    expect(r.hasTransfers).toBe(true);
  });

  it("an unbalanced (one-leg) transfer moves resultNet away from resultBefore", () => {
    const r = computeResults([
      leaf("income", 4000, 4000),
      leaf("expense", 3000, 3000),
      leaf("transfer", -200, 0), // a single categorised leg
    ]);
    expect(r.resultBefore.monthCurrent).toBe(1000);
    // Net folds the residual transfer in: 1000 + (−200) = 800.
    expect(r.resultNet.monthCurrent).toBe(800);
  });

  it("a deficit yields a negative net result and no transfer line", () => {
    const r = computeResults([leaf("income", 2000, 2000), leaf("expense", 2500, 2400)]);
    expect(r.resultNet.monthCurrent).toBe(-500);
    expect(r.hasTransfers).toBe(false);
  });

  it("ignores subtotal (is_parent) rows so a group is not double-counted", () => {
    const parent = { ...leaf("expense", 999, 999), is_parent: true };
    const r = computeResults([parent, leaf("expense", 100, 80)]);
    expect(r.expense.monthCurrent).toBe(100);
  });

  it("carries the YTD figures through the same arithmetic", () => {
    const r = computeResults([
      leaf("income", 100, 100, 1200, 1150),
      leaf("expense", 60, 60, 700, 650),
    ]);
    expect(r.resultBefore.ytdCurrent).toBe(500);
    expect(r.resultBefore.ytdPrevious).toBe(500);
  });

  it("sumLeaves adds only leaves", () => {
    const t = sumLeaves([
      leaf("expense", 100, 50),
      { ...leaf("expense", 999, 999), is_parent: true },
    ]);
    expect(t.monthCurrent).toBe(100);
    expect(t.monthPrevious).toBe(50);
  });
});
