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();
  // Seed one balanced entry per company via admin (bypasses RLS) so the
  // isolation tests below run against populated journal tables.
  const seed = async (company: string, user: string, cash: number, rev: number) => {
    // Wrapped in a transaction so the deferred balance-check trigger (CLAUDE.md
    // §3.1) sees both lines before commit — inserting entry and lines as
    // separate implicit transactions would fail the entry on 0 lines.
    await db.admin.query("BEGIN");
    try {
      const e = await db.admin.query(
        `INSERT INTO journal_entries (company_id, entry_date, memo, created_by)
         VALUES ($1, '2024-01-05', 'seed', $2) RETURNING id`, [company, user]);
      await db.admin.query(
        `INSERT INTO journal_lines (company_id, entry_id, account_id, debit, credit)
         VALUES ($1, $2, $3, '100.0000', 0), ($1, $2, $4, 0, '100.0000')`,
        [company, e.rows[0].id, cash, rev]);
      await db.admin.query("COMMIT");
    } catch (err) {
      await db.admin.query("ROLLBACK").catch(() => {});
      throw err;
    }
  };
  await seed(db.companyA, db.userA, db.accounts["1000"].A, db.accounts["4000"].A);
  await seed(db.companyB, db.userB, db.accounts["1000"].B, db.accounts["4000"].B);
}, 60_000);
afterAll(async () => { await db.close(); });

describe("tenant isolation (CLAUDE.md §3.5, §4)", () => {
  it("a query without a company filter returns only the session company's rows", async () => {
    const c = await db.appClient(db.companyA);
    const res = await c.query("SELECT company_id FROM accounts");
    expect(res.rows.length).toBe(8);
    expect(res.rows.every((r) => r.company_id === db.companyA)).toBe(true);
    const je = await c.query("SELECT company_id FROM journal_entries");
    expect(je.rows.length).toBe(1);
    expect(je.rows[0].company_id).toBe(db.companyA);
    const jl = await c.query("SELECT company_id FROM journal_lines");
    expect(jl.rows.length).toBe(2);
    expect(jl.rows.every((r) => r.company_id === db.companyA)).toBe(true);
  });

  it("with no app.company_id set, every tenant table returns zero rows", async () => {
    const c = await db.appClient(null);
    for (const table of ["companies", "accounts", "journal_entries", "journal_lines"]) {
      const res = await c.query(`SELECT count(*)::int AS n FROM ${table}`);
      expect(res.rows[0].n).toBe(0);
    }
  });

  it("cannot insert a row for another company (RLS WITH CHECK)", async () => {
    const c = await db.appClient(db.companyA);
    await expect(
      c.query(
        `INSERT INTO accounts (company_id, code, name, type)
         VALUES ($1, '9999', 'Sneaky', 'asset')`,
        [db.companyB]),
    ).rejects.toThrow(/row-level security/);
  });

  it("a journal line cannot reference another company's account (composite FK)", 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"].B, credit: "100.0000" }, // B's account!
        ],
      }),
    ).rejects.toThrow(/foreign key|violates/);
  });
});
