import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createTestDb, type TestDb, insertDraftEntry } from "../helpers/db.js";

let db: TestDb;
beforeAll(async () => { db = await createTestDb(); }, 60_000);
afterAll(async () => { await db.close(); });

describe("every journal entry balances (CLAUDE.md §3.1)", () => {
  it("rejects an unbalanced entry at commit", async () => {
    const c = await db.appClient(db.companyA);
    await expect(
      insertDraftEntry(c, db.companyA, db.userA, {
        lines: [
          { accountId: db.accounts["1000"].A, debit: "100.0000" },
          { accountId: db.accounts["4000"].A, credit: "66.0000" },
        ],
      }),
    ).rejects.toThrow(/out of balance/);
  });

  it("rejects an entry with fewer than two lines", async () => {
    const c = await db.appClient(db.companyA);
    // a zero-amount line is blocked by the line CHECK constraint itself
    await expect(
      insertDraftEntry(c, db.companyA, db.userA, {
        lines: [{ accountId: db.accounts["1000"].A, debit: "0.0000" }],
      }),
    ).rejects.toThrow(/check constraint|at least two lines/);
    // one REAL line still fails the minimum-two-lines rule
    await expect(
      insertDraftEntry(c, db.companyA, db.userA, {
        lines: [{ accountId: db.accounts["1000"].A, debit: "50.0000" }],
      }),
    ).rejects.toThrow(/at least two lines/);
  });

  it("rejects an entry with no lines at all", async () => {
    const c = await db.appClient(db.companyA);
    await expect(
      insertDraftEntry(c, db.companyA, db.userA, { lines: [] }),
    ).rejects.toThrow(/at least two lines/);
  });

  it("accepts a balanced entry", async () => {
    const c = await db.appClient(db.companyA);
    const id = await insertDraftEntry(c, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["1000"].A, debit: "250.0000" },
        { accountId: db.accounts["4000"].A, credit: "250.0000" },
      ],
    });
    expect(id).toBeGreaterThan(0);
  });

  it("rejects a line with both debit and credit set", async () => {
    const c = await db.appClient(db.companyA);
    await expect(
      insertDraftEntry(c, db.companyA, db.userA, {
        lines: [
          { accountId: db.accounts["1000"].A, debit: "10.0000", credit: "10.0000" },
          { accountId: db.accounts["4000"].A, credit: "10.0000" },
        ],
      }),
    ).rejects.toThrow(/check constraint|violates/);
  });

  it("moving a line to another entry rechecks the source entry", async () => {
    const c = await db.appClient(db.companyA);
    const e1 = await insertDraftEntry(c, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["1000"].A, debit: "40.0000" },
        { accountId: db.accounts["4000"].A, credit: "40.0000" },
      ],
    });
    const e2 = await insertDraftEntry(c, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["1000"].A, debit: "70.0000" },
        { accountId: db.accounts["4000"].A, credit: "70.0000" },
      ],
    });
    // move BOTH of e1's lines into e2: e2 stays balanced, e1 ends up empty —
    // the source-entry recheck must reject the commit
    await c.query("BEGIN");
    await c.query(
      "UPDATE journal_lines SET entry_id = $1 WHERE entry_id = $2", [e2, e1]);
    await expect(c.query("COMMIT")).rejects.toThrow(/at least two lines/);
  });

  it("the balance-check helper is not callable by the app role", async () => {
    const c2 = await db.appClient(db.companyA);
    await expect(
      c2.query("SELECT assert_entry_balanced($1, 1)", [db.companyB]),
    ).rejects.toThrow(/permission denied/);
  });
});
