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

let db: TestDb;
let c: Client;

beforeAll(async () => {
  db = await createTestDb();
  // pair: A pays on behalf of B -> "Due from Affiliate" (A) / "Due to Affiliate" (B)
  await db.admin.query(
    `INSERT INTO intercompany_pairs
       (from_company_id, to_company_id, due_from_account_id, due_to_account_id)
     VALUES ($1, $2, $3, $4)`,
    [db.companyA, db.companyB, db.accounts["1200"].A, db.accounts["2200"].B]);
  c = await db.appClient(db.companyA);
}, 60_000);
afterAll(async () => { await db.close(); });

describe("intercompany transactions (CLAUDE.md §4)", () => {
  it("writes both halves in a single transaction, both balanced and posted", async () => {
    const r = await c.query(
      `SELECT * FROM post_intercompany($1, $2, '750.0000', '2024-03-01',
              'A paid vendor invoice for B', $3, $4, $5)`,
      [db.companyA, db.companyB, db.userA,
       db.accounts["1000"].A,     // A's offset: cash going out
       db.accounts["6000"].B]);   // B's offset: the expense it owes for
    const { from_entry_id, to_entry_id } = r.rows[0];

    const cB = await db.appClient(db.companyB);
    const eA = await c.query(
      "SELECT status FROM journal_entries WHERE id = $1", [from_entry_id]);
    const eB = await cB.query(
      "SELECT status FROM journal_entries WHERE id = $1", [to_entry_id]);
    expect(eA.rows[0].status).toBe("posted");
    expect(eB.rows[0].status).toBe("posted");

    const dueFrom = await c.query(
      "SELECT balance FROM trial_balance($1, '2024-03-31') WHERE code = '1200'",
      [db.companyA]);
    const dueTo = await cB.query(
      "SELECT balance FROM trial_balance($1, '2024-03-31') WHERE code = '2200'",
      [db.companyB]);
    expect(dueFrom.rows[0].balance).toBe("750.0000");
    expect(dueTo.rows[0].balance).toBe("-750.0000"); // credit balance, debit-positive storage
  });

  it("the caller's session company scope is restored afterward", async () => {
    const r = await c.query("SELECT current_setting('app.company_id', true) AS s");
    expect(r.rows[0].s).toBe(db.companyA);
    // and RLS still scopes this connection to company A only
    const res = await c.query("SELECT DISTINCT company_id FROM journal_entries");
    expect(res.rows.length).toBe(1);
    expect(res.rows[0].company_id).toBe(db.companyA);
  });

  it("refuses when no pair is configured for the direction", async () => {
    await expect(
      c.query(
        `SELECT * FROM post_intercompany($1, $2, '10.0000', '2024-03-01', 'nope',
                $3, $4, $5)`,
        [db.companyB, db.companyA, db.userA,
         db.accounts["1000"].B, db.accounts["6000"].A]),
    ).rejects.toThrow(/no intercompany pair/);
  });

  it("refuses when the session company is neither side", async () => {
    const outsider = await db.admin.query(
      `INSERT INTO companies (name) VALUES ('Gamma LLC') RETURNING id`);
    const cG = await db.appClient(outsider.rows[0].id);
    await expect(
      cG.query(
        `SELECT * FROM post_intercompany($1, $2, '10.0000', '2024-03-01', 'nope',
                $3, $4, $5)`,
        [db.companyA, db.companyB, db.userA,
         db.accounts["1000"].A, db.accounts["6000"].B]),
    ).rejects.toThrow(/not a party/);
  });

  it("refuses a non-positive amount", async () => {
    await expect(
      c.query(
        `SELECT * FROM post_intercompany($1, $2, '0.0000', '2024-03-01', 'zero',
                $3, $4, $5)`,
        [db.companyA, db.companyB, db.userA,
         db.accounts["1000"].A, db.accounts["6000"].B]),
    ).rejects.toThrow(/must be positive/);
  });

  it("one leg of an intercompany posting cannot be reversed alone", async () => {
    const r = await c.query(
      `SELECT * FROM post_intercompany($1, $2, '200.0000', '2024-04-01', 'leg test',
              $3, $4, $5)`,
      [db.companyA, db.companyB, db.userA,
       db.accounts["1000"].A, db.accounts["6000"].B]);
    await expect(
      c.query("SELECT reverse_entry($1, $2, '2024-04-02', $3)",
        [db.companyA, r.rows[0].from_entry_id, db.userA]),
    ).rejects.toThrow(/use reverse_intercompany/);
    // stash for the next test
    (globalThis as any).__legEntry = r.rows[0].from_entry_id;
  });

  it("reverse_intercompany reverses both legs and restores both books", async () => {
    const cB = await db.appClient(db.companyB);
    const before = {
      a: (await c.query(
        "SELECT balance FROM trial_balance($1, NULL) WHERE code = '1200'",
        [db.companyA])).rows[0].balance,
      b: (await cB.query(
        "SELECT balance FROM trial_balance($1, NULL) WHERE code = '2200'",
        [db.companyB])).rows[0].balance,
    };
    const legEntry = (globalThis as any).__legEntry;
    await c.query("SELECT * FROM reverse_intercompany($1, $2, '2024-04-03', $3)",
      [db.companyA, legEntry, db.userA]);
    const afterA = (await c.query(
      "SELECT balance FROM trial_balance($1, NULL) WHERE code = '1200'",
      [db.companyA])).rows[0].balance;
    const afterB = (await cB.query(
      "SELECT balance FROM trial_balance($1, NULL) WHERE code = '2200'",
      [db.companyB])).rows[0].balance;
    // the 200.0000 leg pair is fully unwound from BOTH books
    const deltaA = (await c.query(
      "SELECT ($1::numeric(19,4) - $2::numeric(19,4))::numeric(19,4) AS d",
      [before.a, afterA])).rows[0].d;
    const deltaB = (await cB.query(
      "SELECT ($1::numeric(19,4) - $2::numeric(19,4))::numeric(19,4) AS d",
      [before.b, afterB])).rows[0].d;
    expect(deltaA).toBe("200.0000");
    expect(deltaB).toBe("-200.0000");
    // and it cannot be double-reversed
    await expect(
      c.query("SELECT * FROM reverse_intercompany($1, $2, '2024-04-04', $3)",
        [db.companyA, legEntry, db.userA]),
    ).rejects.toThrow(/already been reversed/);
  });
});
