import { describe, it, expect, vi } from "vitest";
import { bulkInstructDeals } from "./bulkInstructDeals";
import type { RunBulkInstructTx, SyncInstructedForDeals } from "./bulkInstructDeals";

const noopSync: SyncInstructedForDeals = async () => ({ ok: true });

function makeSuccessTx(): RunBulkInstructTx {
  return async ({ deals }) => ({
    instructedRowIds: deals.map((_, i) => BigInt(i + 1)),
    approvedMeasuresByDeal: Object.fromEntries(
      deals.map((d) => [d.dealId, d.measureNames]),
    ),
  });
}

describe("bulkInstructDeals", () => {
  it("returns ok:false when deals array is empty", async () => {
    const result = await bulkInstructDeals({
      deals: [],
      userId: 1n,
      deps: { runBulkInstructTx: makeSuccessTx(), syncInstructed: noopSync },
    });
    expect(result).toEqual({ ok: false, error: "deals must not be empty" });
  });

  it("returns ok:false when a deal has empty measureNames", async () => {
    const result = await bulkInstructDeals({
      deals: [{ dealId: "deal-1", measureNames: [] }],
      userId: 1n,
      deps: { runBulkInstructTx: makeSuccessTx(), syncInstructed: noopSync },
    });
    expect(result).toMatchObject({ ok: false });
  });

  it("returns ok:false for unknown measure name", async () => {
    const result = await bulkInstructDeals({
      deals: [{ dealId: "deal-1", measureNames: ["Not A Real Measure"] }],
      userId: 1n,
      deps: { runBulkInstructTx: makeSuccessTx(), syncInstructed: noopSync },
    });
    expect(result).toMatchObject({ ok: false, error: expect.stringContaining("Unknown measure") });
  });

  it("returns ok:true when all deals succeed", async () => {
    const result = await bulkInstructDeals({
      deals: [
        { dealId: "deal-1", measureNames: ["ASHP", "CWI"] },
        { dealId: "deal-2", measureNames: ["EWI"] },
      ],
      userId: 1n,
      deps: { runBulkInstructTx: makeSuccessTx(), syncInstructed: noopSync },
    });
    expect(result).toMatchObject({ ok: true, hubspotSync: "ok" });
  });

  it("returns ok:false when tx throws (atomic rollback)", async () => {
    const failingTx: RunBulkInstructTx = async () => {
      throw new Error("FK violation");
    };
    const result = await bulkInstructDeals({
      deals: [{ dealId: "deal-1", measureNames: ["ASHP"] }],
      userId: 1n,
      deps: { runBulkInstructTx: failingTx, syncInstructed: noopSync },
    });
    expect(result).toMatchObject({ ok: false, error: "FK violation" });
  });

  it("returns ok:true with hubspotSync:'failed' when sync fails", async () => {
    const failSync: SyncInstructedForDeals = async () => ({
      ok: false,
      error: "HubSpot rate limit",
    });
    const result = await bulkInstructDeals({
      deals: [{ dealId: "deal-1", measureNames: ["ASHP"] }],
      userId: 1n,
      deps: { runBulkInstructTx: makeSuccessTx(), syncInstructed: failSync },
    });
    expect(result).toMatchObject({ ok: true, hubspotSync: "failed", hubspotError: "HubSpot rate limit" });
  });

  it("passes all deals to the tx in one call", async () => {
    const txSpy = vi.fn(makeSuccessTx());
    await bulkInstructDeals({
      deals: [
        { dealId: "deal-1", measureNames: ["ASHP"] },
        { dealId: "deal-2", measureNames: ["CWI"] },
      ],
      userId: 1n,
      deps: { runBulkInstructTx: txSpy, syncInstructed: noopSync },
    });
    expect(txSpy).toHaveBeenCalledOnce();
    expect(txSpy.mock.calls[0][0].deals).toHaveLength(2);
  });
});
