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

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

async function balances(): Promise<Map<number, string>> {
  const res = await c.query(
    `SELECT l.account_id, sum(l.debit - l.credit)::numeric(19,4) AS bal
       FROM journal_lines l
       JOIN journal_entries e
         ON e.company_id = l.company_id AND e.id = l.entry_id
      WHERE e.status = 'posted'
      GROUP BY l.account_id`);
  return new Map(res.rows.map((r) => [Number(r.account_id), r.bal]));
}

describe("reversal (CLAUDE.md §3.2, §11)", () => {
  it("reversing an entry restores every affected account balance exactly", async () => {
    const before = await balances();
    const id = await insertDraftEntry(c, db.companyA, db.userA, {
      date: "2024-02-01",
      lines: [
        { accountId: db.accounts["1100"].A, debit: "1234.5600" },
        { accountId: db.accounts["4000"].A, credit: "1234.5600" },
      ],
    });
    await postEntry(c, db.companyA, id);

    const rev = await c.query(
      "SELECT reverse_entry($1, $2, '2024-02-02', $3) AS rid",
      [db.companyA, id, db.userA]);
    const revId = Number(rev.rows[0].rid);
    expect(revId).toBeGreaterThan(0);

    const after = await balances();
    for (const acct of [db.accounts["1100"].A, db.accounts["4000"].A]) {
      expect(after.get(acct) ?? "0.0000").toBe(before.get(acct) ?? "0.0000");
    }
    // reversal is posted, linked, and mirror-imaged
    const meta = await c.query(
      `SELECT status, reverses_entry_id FROM journal_entries WHERE id = $1`, [revId]);
    expect(meta.rows[0].status).toBe("posted");
    expect(Number(meta.rows[0].reverses_entry_id)).toBe(id);
  });

  it("only posted entries can be reversed", async () => {
    const draft = await insertDraftEntry(c, db.companyA, db.userA, {
      lines: [
        { accountId: db.accounts["6000"].A, debit: "10.0000" },
        { accountId: db.accounts["1000"].A, credit: "10.0000" },
      ],
    });
    await expect(
      c.query("SELECT reverse_entry($1, $2, '2024-02-02', $3)",
        [db.companyA, draft, db.userA]),
    ).rejects.toThrow(/only posted/);
  });

  it("the reversal lines are the exact mirror image", async () => {
    const id = await insertDraftEntry(c, db.companyA, db.userA, {
      date: "2024-02-05",
      lines: [
        { accountId: db.accounts["1000"].A, debit: "10.0000" },
        { accountId: db.accounts["1100"].A, debit: "5.0000" },
        { accountId: db.accounts["4000"].A, credit: "15.0000" },
      ],
    });
    await postEntry(c, db.companyA, id);
    const rev = await c.query(
      "SELECT reverse_entry($1, $2, '2024-02-06', $3) AS rid",
      [db.companyA, id, db.userA]);
    const lines = await c.query(
      `SELECT account_id, debit, credit FROM journal_lines
        WHERE entry_id = $1 ORDER BY account_id`, [rev.rows[0].rid]);
    expect(lines.rows).toEqual([
      { account_id: String(db.accounts["1000"].A), debit: "0.0000", credit: "10.0000" },
      { account_id: String(db.accounts["1100"].A), debit: "0.0000", credit: "5.0000" },
      { account_id: String(db.accounts["4000"].A), debit: "15.0000", credit: "0.0000" },
    ]);
  });

  it("an entry cannot be reversed twice", async () => {
    const id = await insertDraftEntry(c, db.companyA, db.userA, {
      date: "2024-03-01",
      lines: [
        { accountId: db.accounts["1000"].A, debit: "80.0000" },
        { accountId: db.accounts["4000"].A, credit: "80.0000" },
      ],
    });
    await postEntry(c, db.companyA, id);
    await c.query("SELECT reverse_entry($1, $2, '2024-03-02', $3)",
      [db.companyA, id, db.userA]);
    await expect(
      c.query("SELECT reverse_entry($1, $2, '2024-03-03', $3)",
        [db.companyA, id, db.userA]),
    ).rejects.toThrow(/already been reversed/);
  });

  it("a reversal cannot be dated before the original entry", async () => {
    const id = await insertDraftEntry(c, db.companyA, db.userA, {
      date: "2024-04-10",
      lines: [
        { accountId: db.accounts["1000"].A, debit: "60.0000" },
        { accountId: db.accounts["4000"].A, credit: "60.0000" },
      ],
    });
    await postEntry(c, db.companyA, id);
    await expect(
      c.query("SELECT reverse_entry($1, $2, '2024-04-01', $3)",
        [db.companyA, id, db.userA]),
    ).rejects.toThrow(/cannot precede/);
  });

  it("reverse_entry refuses a mismatched session company", async () => {
    const cB = await db.appClient(db.companyB);
    await expect(
      cB.query("SELECT reverse_entry($1, $2, '2024-02-02', $3)",
        [db.companyA, 1, db.userA]),
    ).rejects.toThrow(/session company/);
  });
});
