import { Prisma } from "@jorgecuadros/database";
import {
  CASH_JOURNAL_SOURCE_DB,
  CASH_JOURNAL_SOURCE_TABLES,
  NOT_CASH_JOURNAL,
  notCashJournal,
} from "./billing.service";

/**
 * `EFECTIVO` is the office's paper receipt book, and every receipt in it is
 * also posted to the utilities ledger as `C<folio>`. Both copies were imported
 * into one `transactions` table, so a balance that reads the journal counts
 * each counter payment twice — 1,094,347.78 MXN of phantom credit book-wide,
 * and 3,500.00 of it on NUMid 10 alone.
 *
 * These fail silently in the worst way: the numbers stay plausible, they are
 * just too generous to the customer. Two shapes of mistake are easy to make
 * here and both are covered below — dropping the database qualifier (which
 * erases the insurance line's only ledger) and writing the exclusion as a bare
 * `NOT IN` (which erases every app-captured row).
 */
describe("cash journal exclusion", () => {
  describe("raw SQL form", () => {
    it("binds the source database rather than interpolating it", () => {
      expect(NOT_CASH_JOURNAL.values).toContain(CASH_JOURNAL_SOURCE_DB);
    });

    it("qualifies the table names with the database they came from", () => {
      // `SEGUROS 16_be` has its own EFECTIVO and it is the insurance line's
      // ONLY ledger — nothing posts it anywhere else. Matching on the table
      // name alone erases 55,444.95 USD and 63,957.78 MXN across 102 customers.
      expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceDb <>");
      expect(NOT_CASH_JOURNAL.values).toContain(CASH_JOURNAL_SOURCE_DB);
    });

    it("spells both null cases out instead of relying on NOT IN", () => {
      // `NULL NOT IN (...)` is NULL, not true. Without these branches every
      // app-captured row — the ones staff key in by hand — drops out of the
      // balance while still showing in the movement browser.
      expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceDb IS NULL");
      expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceTable IS NULL");
    });

    it("covers the whole cash family, not just EFECTIVO", () => {
      for (const table of CASH_JOURNAL_SOURCE_TABLES) {
        expect(NOT_CASH_JOURNAL.values).toContain(table);
      }
    });

    it("is a single parenthesised term, safe to AND into a WHERE clause", () => {
      // It is composed as `... AND ${NOT_CASH_JOURNAL} AND ...`. An unbracketed
      // OR chain would swallow every condition after it and silently widen the
      // whole query to the entire table.
      const sql = NOT_CASH_JOURNAL.sql.trim();
      expect(sql.startsWith("(")).toBe(true);
      expect(sql.endsWith(")")).toBe(true);
    });
  });

  describe("Prisma form", () => {
    it("matches the raw form's terms so the two cannot drift apart", () => {
      const branches = notCashJournal().OR as Prisma.TransactionWhereInput[];
      expect(branches).toEqual([
        { legacySourceDb: null },
        { legacySourceDb: { not: CASH_JOURNAL_SOURCE_DB } },
        { legacySourceTable: null },
        { legacySourceTable: { notIn: [...CASH_JOURNAL_SOURCE_TABLES] } },
      ]);
    });

    it("returns a fresh object each call", () => {
      // It is spread into `AND: [...]` arrays that Prisma may mutate; a shared
      // singleton would leak one query's filters into the next.
      expect(notCashJournal()).not.toBe(notCashJournal());
    });
  });
});
