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

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

function simpleSpec(db: TestDb, company: "A" | "B", date = "2024-03-10") {
  return {
    date,
    lines: [
      { accountId: db.accounts["1000"][company], debit: "100.0000" },
      { accountId: db.accounts["4000"][company], credit: "100.0000" },
    ],
  };
}

describe("posting and per-company numbering (CLAUDE.md §4)", () => {
  it("each company's journal numbers independently from 1", async () => {
    const cA = await db.appClient(db.companyA);
    const cB = await db.appClient(db.companyB);
    const a1 = await insertDraftEntry(cA, db.companyA, db.userA, simpleSpec(db, "A"));
    const a2 = await insertDraftEntry(cA, db.companyA, db.userA, simpleSpec(db, "A"));
    const b1 = await insertDraftEntry(cB, db.companyB, db.userB, simpleSpec(db, "B"));
    expect(await postEntry(cA, db.companyA, a1)).toBe(1);
    expect(await postEntry(cA, db.companyA, a2)).toBe(2);
    expect(await postEntry(cB, db.companyB, b1)).toBe(1); // B starts at 1, not 3
  });

  it("posting twice raises", async () => {
    const c = await db.appClient(db.companyA);
    const id = await insertDraftEntry(c, db.companyA, db.userA, simpleSpec(db, "A"));
    await postEntry(c, db.companyA, id);
    await expect(postEntry(c, db.companyA, id)).rejects.toThrow(/already posted/);
  });

  it("posting writes an audit_log row with real user attribution", async () => {
    const c = await db.appClient(db.companyA);
    const id = await insertDraftEntry(c, db.companyA, db.userA, simpleSpec(db, "A"));
    await postEntry(c, db.companyA, id);
    const res = await c.query(
      `SELECT user_id FROM audit_log
        WHERE entity = 'journal_entry' AND entity_id = $1 AND action = 'post'`,
      [String(id)]);
    expect(res.rows.length).toBe(1);
    expect(res.rows[0].user_id).toBe(db.userA);
  });

  it("the app role cannot bypass post_entry()", async () => {
    const c = await db.appClient(db.companyA);
    const id = await insertDraftEntry(c, db.companyA, db.userA, simpleSpec(db, "A"));
    // direct flip to posted: denied by column-level grants
    await expect(
      c.query(
        `UPDATE journal_entries
            SET status = 'posted', entry_no = 99999, posted_at = now()
          WHERE id = $1`, [id]),
    ).rejects.toThrow(/permission denied/);
    // inserting an already-posted entry: rejected by trigger
    await c.query("BEGIN");
    await expect(
      c.query(
        `INSERT INTO journal_entries
           (company_id, entry_date, status, entry_no, created_by)
         VALUES ($1, '2024-03-10', 'posted', 12345, $2)`,
        [db.companyA, db.userA]),
    ).rejects.toThrow(/use post_entry/);
    await c.query("ROLLBACK");
    // posting someone else's company id through the function: guard refuses
    await expect(
      c.query("SELECT post_entry($1, $2)", [db.companyB, id]),
    ).rejects.toThrow(/session company/);
  });
});

describe("period locks (CLAUDE.md §3.3)", () => {
  it("cannot post an entry dated inside a locked period", async () => {
    const c = await db.appClient(db.companyA);
    await c.query(
      `INSERT INTO period_locks (company_id, locked_through, created_by)
       VALUES ($1, '2024-06-30', $2)`, [db.companyA, db.userA]);
    const id = await insertDraftEntry(c, db.companyA, db.userA,
      simpleSpec(db, "A", "2024-06-30"));
    await expect(postEntry(c, db.companyA, id)).rejects.toThrow(/locked period/);
    // one day after the lock is fine
    const ok = await insertDraftEntry(c, db.companyA, db.userA,
      simpleSpec(db, "A", "2024-07-01"));
    await expect(postEntry(c, db.companyA, ok)).resolves.toBeGreaterThan(0);
  });

  it("the effective lock is the LATEST row, and locks don't leak across companies", async () => {
    const c = await db.appClient(db.companyA);
    // extend the lock; earlier row no longer effective
    await c.query(
      `INSERT INTO period_locks (company_id, locked_through, created_by)
       VALUES ($1, '2024-07-31', $2)`, [db.companyA, db.userA]);
    const id = await insertDraftEntry(c, db.companyA, db.userA,
      simpleSpec(db, "A", "2024-07-15"));
    await expect(postEntry(c, db.companyA, id)).rejects.toThrow(/locked period/);
    // company B is unaffected
    const cB = await db.appClient(db.companyB);
    const b = await insertDraftEntry(cB, db.companyB, db.userB,
      simpleSpec(db, "B", "2024-06-15"));
    await expect(postEntry(cB, db.companyB, b)).resolves.toBeGreaterThan(0);
  });

  it("period_locks is append-only", async () => {
    const c = await db.appClient(db.companyA);
    await expect(
      c.query("UPDATE period_locks SET locked_through = '2000-01-01'"),
    ).rejects.toThrow(/append-only|denied/);
    await expect(
      c.query("DELETE FROM period_locks"),
    ).rejects.toThrow(/append-only|denied/);
    // superuser bypasses grants, so this exercises the trigger itself
    await expect(
      db.admin.query("UPDATE period_locks SET locked_through = '2000-01-01'"),
    ).rejects.toThrow(/append-only/);
    await expect(
      db.admin.query("DELETE FROM period_locks"),
    ).rejects.toThrow(/append-only/);
  });
});
