import { describe, it, expect } from "vitest";
import { Prisma } from "@prisma/client";
import { invoiceTotalWithVat } from "../services/invoices.service";

// `invoiceTotalWithVat` receives a Prisma row whose money columns are real
// `Prisma.Decimal` instances (the model maps `@db.Decimal` → Decimal). Using
// real Decimals here — instead of the old `{ toNumber: () => N }` duck types —
// means a regression in genuine Decimal handling (e.g. swapping `.toNumber()`
// for `Number(decimal)`) is actually caught.

const dec = (n: number | string) => new Prisma.Decimal(n);

// Helper so each test reads as a small declarative table of lines.
function buildInvoice(opts: {
  applyVat: boolean;
  vatRate: number | null;
  currency?: string;
  items: Array<{
    quantity: number | null;
    unit_price: number | null;
    vat_rate: number | null;
  }>;
}) {
  return {
    apply_vat: opts.applyVat,
    vat_rate: opts.vatRate === null ? null : dec(opts.vatRate),
    currency: opts.currency ?? "CZK",
    invoice_items: opts.items.map((i) => ({
      quantity: i.quantity === null ? null : dec(i.quantity),
      unit_price: i.unit_price === null ? null : dec(i.unit_price),
      vat_rate: i.vat_rate === null ? null : dec(i.vat_rate),
    })),
  };
}

describe("invoiceTotalWithVat", () => {
  it("calculates subtotal without VAT when apply_vat is false", () => {
    const inv = buildInvoice({
      applyVat: false,
      vatRate: 21,
      items: [{ quantity: 2, unit_price: 100, vat_rate: 21 }],
    });
    expect(invoiceTotalWithVat(inv)).toBe(200);
  });

  it("rounds each line VAT to 2 decimals before accumulation", () => {
    // 3 items @ 33.33 with 21% VAT
    // Line VAT = 33.33 * 0.21 = 6.9993 -> rounded to 7.00
    // Total VAT = 7.00 * 3 = 21.00
    // Subtotal = 33.33 * 3 = 99.99
    // Total = 99.99 + 21.00 = 120.99
    const inv = buildInvoice({
      applyVat: true,
      vatRate: 21,
      items: Array.from({ length: 3 }, () => ({
        quantity: 1,
        unit_price: 33.33,
        vat_rate: 21,
      })),
    });
    expect(invoiceTotalWithVat(inv)).toBe(120.99);
  });

  it("treats a null quantity (and null unit_price) as 0 for that line", () => {
    // The line is genuinely null here (not 0): quantity?.toNumber() === undefined
    // -> Number(undefined) is NaN -> `|| 0` -> base 0. A second, well-formed
    // line proves the null line contributes nothing while the rest still totals.
    const inv = buildInvoice({
      applyVat: true,
      vatRate: 21,
      items: [
        { quantity: null, unit_price: null, vat_rate: 21 },
        { quantity: 1, unit_price: 100, vat_rate: 21 }, // base 100, vat 21
      ],
    });
    expect(invoiceTotalWithVat(inv)).toBe(121);
  });

  it("returns 0 when every line is null", () => {
    const inv = buildInvoice({
      applyVat: true,
      vatRate: 21,
      items: [{ quantity: null, unit_price: null, vat_rate: null }],
    });
    expect(invoiceTotalWithVat(inv)).toBe(0);
  });

  it("applies mixed per-line vat_rate values, falling back to the invoice rate when a line rate is 0/absent", () => {
    // Line 1: 2 x 100 @ 21% -> base 200, vat 42
    // Line 2: 1 x 50  @ 12% -> base 50,  vat 6
    // Line 3: 1 x 100 @ 0%  -> a line rate of 0 falls through to the invoice
    //         default (15%) per the `|| inv.vat_rate || 21` semantics -> vat 15
    // Subtotal = 350, VAT = 63, Total = 413
    const inv = buildInvoice({
      applyVat: true,
      vatRate: 15,
      items: [
        { quantity: 2, unit_price: 100, vat_rate: 21 },
        { quantity: 1, unit_price: 50, vat_rate: 12 },
        { quantity: 1, unit_price: 100, vat_rate: 0 },
      ],
    });
    expect(invoiceTotalWithVat(inv)).toBe(413);
  });

  it("handles a negative (discount) line correctly", () => {
    // Line 1: 1 x 1000 @ 21% -> base 1000, vat 210
    // Line 2 (discount): 1 x -200 @ 21% -> base -200, vat -42
    // Subtotal = 800, VAT = 168, Total = 968
    const inv = buildInvoice({
      applyVat: true,
      vatRate: 21,
      items: [
        { quantity: 1, unit_price: 1000, vat_rate: 21 },
        { quantity: 1, unit_price: -200, vat_rate: 21 },
      ],
    });
    expect(invoiceTotalWithVat(inv)).toBe(968);
  });
});
