import { CustomersService } from "./customers.service";
import { BALANCE_FORWARD_TYPE } from "../billing/billing.service";

/**
 * The /clientes/:id ledger card is titled "Estado de cuenta" and links straight
 * to the statement, so its per-line totals must be the statement's numbers.
 *
 * They were a raw lifetime sum — no floor, no source exclusion — which
 * double-counted the pre-cutover history each BALANCE FORWARD row absorbs.
 * Importing prior periods made it visibly worse: every closed year is now held
 * a second time as its own tagged copy, so an unfloored sum adds each one on
 * top of the opening balance that already contains it.
 */
describe("customer file ledger card", () => {
  function serviceWith(floor: Date | null) {
    const groupBy = jest.fn().mockResolvedValue([]);
    const prisma = {
      customer: {
        findUnique: jest.fn().mockResolvedValue({ id: "c1", transactions: [] }),
      },
      transaction: {
        findFirst: jest
          .fn()
          .mockResolvedValue(floor ? { transactionDate: floor } : null),
        groupBy,
      },
    };
    return {
      service: new CustomersService(prisma as never),
      prisma,
      groupBy,
    };
  }

  it("takes the same balance floor the statement takes", async () => {
    const floor = new Date("2026-01-01T00:00:00Z");
    const { service, prisma, groupBy } = serviceWith(floor);

    await service.detail("c1");

    expect(prisma.transaction.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          type: { nameEn: BALANCE_FORWARD_TYPE },
        }),
      }),
    );
    expect(groupBy.mock.calls[0][0].where).toMatchObject({
      transactionDate: { gte: floor },
    });
  });

  it("applies no floor when the customer never had an opening balance", async () => {
    // 102 customers have no BALANCE FORWARD row at all. Inventing a floor for
    // them would hide their whole ledger.
    const { service, groupBy } = serviceWith(null);

    await service.detail("c1");

    expect(groupBy.mock.calls[0][0].where).not.toHaveProperty("transactionDate");
  });

  it("counts an archive as history but never as current", async () => {
    // The floor alone is not enough: a customer floored by an archive clears
    // it with every row of that archive, and the rows archives spill into the
    // following January clear any floor. But excluding archives outright is
    // wrong too — below the year start they are the only carry a
    // floored-by-archive customer has (NUMid 295, 785.46).
    const { service, groupBy } = serviceWith(new Date("2026-01-01T00:00:00Z"));

    await service.detail("c1");

    const and = groupBy.mock.calls[0][0].where.AND;
    const rule = and.find((c: { OR?: unknown[] }) =>
      JSON.stringify(c).includes("datos2@"),
    );
    expect(rule.OR).toEqual([
      { legacySourceTable: null },
      { legacySourceTable: { not: { startsWith: "datos2@" } } },
      { transactionDate: { lt: expect.any(Date) } },
    ]);
  });

  it("keeps the cash-source exclusion so it reads like the statement", async () => {
    const { service, groupBy } = serviceWith(new Date("2026-01-01T00:00:00Z"));

    await service.detail("c1");

    const and = groupBy.mock.calls[0][0].where.AND;
    const sourceRule = and.find((c: { OR?: unknown[] }) =>
      JSON.stringify(c).includes("EFECTIVO"),
    );
    expect(sourceRule).toBeDefined();
  });

  it("drops outstanding rows, as every balance does", async () => {
    const { service, groupBy } = serviceWith(null);

    await service.detail("c1");

    expect(groupBy.mock.calls[0][0].where).toMatchObject({
      outstanding: false,
    });
  });

  it("keeps archives out of the year's movement list too", async () => {
    // datos2@2024 carries rows dated into 2026; a date test alone would show
    // them as current-year movements next to the live ledger's own copy.
    const { service, prisma } = serviceWith(null);

    await service.detail("c1");

    const include = prisma.customer.findUnique.mock.calls[0][0].include;
    expect(include.transactions.where.OR).toEqual([
      { legacySourceTable: null },
      { legacySourceTable: { not: { startsWith: "datos2@" } } },
      // Nothing below yearStart reaches this list, so the third branch never
      // admits an archive row here — it is carried for one shared rule.
      { transactionDate: { lt: expect.any(Date) } },
    ]);
  });
});
