import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createTestDb, type TestDb, insertDraftEntry, postEntry } from "../helpers/db.js";
// Races below hold a statement open across an await. inFlight() attaches its
// rejection handler at creation time, which is what stops an early failure from
// becoming an unhandled rejection that silently kills this whole worker — see
// the header of tests/helpers/in-flight.ts.
import { inFlight } from "../helpers/in-flight.js";
import type { Client } from "pg";

let db: TestDb;
let c: Client;
let postedId: number;

beforeAll(async () => {
  db = await createTestDb();
  c = await db.appClient(db.companyA);
  postedId = await insertDraftEntry(c, db.companyA, db.userA, {
    lines: [
      { accountId: db.accounts["1000"].A, debit: "500.0000" },
      { accountId: db.accounts["4000"].A, credit: "500.0000" },
    ],
  });
  await postEntry(c, db.companyA, postedId);
}, 60_000);
afterAll(async () => { await db.close(); });

describe("posted entries are immutable (CLAUDE.md §3.2)", () => {
  it("UPDATE on a posted entry raises", async () => {
    await expect(
      c.query("UPDATE journal_entries SET memo = 'edited' WHERE id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
  });

  it("DELETE on a posted entry raises", async () => {
    await expect(
      c.query("DELETE FROM journal_entries WHERE id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
  });

  it("UPDATE on a posted entry's lines raises", async () => {
    await expect(
      c.query("UPDATE journal_lines SET debit = '999.0000' WHERE entry_id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
  });

  it("DELETE on a posted entry's lines raises", async () => {
    await expect(
      c.query("DELETE FROM journal_lines WHERE entry_id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
  });

  it("INSERTing new lines into a posted entry raises", async () => {
    await expect(
      c.query(
        `INSERT INTO journal_lines (company_id, entry_id, account_id, debit, credit)
         VALUES ($1, $2, $3, '1.0000', '0')`,
        [db.companyA, postedId, db.accounts["6000"].A]),
    ).rejects.toThrow(/immutable/);
  });

  it("even the superuser cannot modify a posted entry", async () => {
    await expect(
      db.admin.query("UPDATE journal_entries SET memo = 'sneaky' WHERE id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
    await expect(
      db.admin.query("DELETE FROM journal_lines WHERE entry_id = $1", [postedId]),
    ).rejects.toThrow(/immutable/);
  });

  it("TRUNCATE is forbidden even for the superuser", async () => {
    await expect(db.admin.query("TRUNCATE journal_lines"))
      .rejects.toThrow(/TRUNCATE is not allowed|foreign key/);
    await expect(db.admin.query("TRUNCATE period_locks"))
      .rejects.toThrow(/TRUNCATE is not allowed/);
  });

  it("a line mutation cannot race past a concurrent post", async () => {
    const a = await db.appClient(db.companyA);
    const b = await db.appClient(db.companyA);
    const id = await insertDraftEntry(a, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["1000"].A, debit: "30.0000" },
        { accountId: db.accounts["4000"].A, credit: "30.0000" },
      ],
    });
    const { rows } = await a.query(
      "SELECT id FROM journal_lines WHERE entry_id = $1 LIMIT 1", [id]);
    await a.query("BEGIN");
    await a.query("SELECT post_entry($1, $2)", [db.companyA, id]);
    // b blocks on the entry row lock until a commits, then must see 'posted'
    const pending = inFlight(b.query(
      "UPDATE journal_lines SET memo = 'race' WHERE id = $1", [rows[0].id]));
    await new Promise((r) => setTimeout(r, 300));
    // The comment above is now an assertion: B must still be waiting on the
    // entry's row lock. A B that had already updated the line would mean an
    // edit CAN slip past a concurrent post — CLAUDE.md §3.2, the invariant
    // this file exists for.
    pending.stillBlocked("B's UPDATE journal_lines");
    await a.query("COMMIT");
    await expect(pending.settled()).rejects.toThrow(/immutable/);
  });

  it("drafts remain editable and deletable", async () => {
    const draft = await insertDraftEntry(c, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["6000"].A, debit: "20.0000" },
        { accountId: db.accounts["1000"].A, credit: "20.0000" },
      ],
    });
    await c.query("UPDATE journal_entries SET memo = 'still a draft' WHERE id = $1", [draft]);
    await c.query("BEGIN");
    await c.query("DELETE FROM journal_lines WHERE entry_id = $1", [draft]);
    await c.query("DELETE FROM journal_entries WHERE id = $1", [draft]);
    await c.query("COMMIT");
  });
});
