import { describe, expect, it, vi } from "vitest";
import { createPibiRequests, PIBI_ORDERED_TEXT_PROP } from "./createPibiRequests";
import type { RunCreatePibiTx, SyncMeasuresField, StampPushedAt } from "./createPibiRequests";

function makeDeps(overrides?: {
  txResult?: { insertedRowIds: bigint[]; allMeasureNames: string[] };
  txError?: Error;
  syncResult?: { ok: true } | { ok: false; error: string };
  stampError?: Error;
}) {
  const txResult = overrides?.txResult ?? {
    insertedRowIds: [1n, 2n],
    allMeasureNames: ["CWI", "Loft insulation"],
  };

  const runCreateTx: RunCreatePibiTx = vi.fn(async () => {
    if (overrides?.txError) throw overrides.txError;
    return txResult;
  });

  const syncMeasuresField: SyncMeasuresField = vi.fn(async () => {
    return overrides?.syncResult ?? ({ ok: true } as const);
  });

  const stampPushedAt: StampPushedAt = vi.fn(async () => {
    if (overrides?.stampError) throw overrides.stampError;
  });

  return { runCreateTx, syncMeasuresField, stampPushedAt };
}

describe("createPibiRequests — happy path", () => {
  it("inserts rows, syncs all deal measures to HubSpot, stamps pushed_at", async () => {
    const orderedAt = new Date("2026-05-06T10:00:00Z");
    const deps = makeDeps({
      txResult: { insertedRowIds: [10n, 11n], allMeasureNames: ["CWI", "Loft insulation"] },
    });

    const result = await createPibiRequests({
      dealId: "deal-1",
      portfolioId: 42n,
      measureNames: ["CWI", "Loft insulation"],
      orderedAt,
      userId: 5n,
      deps,
    });

    expect(result).toMatchObject({
      ok: true,
      insertedRowIds: [10n, 11n],
      hubspotSync: "ok",
    });

    expect(deps.runCreateTx).toHaveBeenCalledWith({
      dealId: "deal-1",
      portfolioId: 42n,
      measureNames: ["CWI", "Loft insulation"],
      orderedAt,
      userId: 5n,
    });

    expect(deps.syncMeasuresField).toHaveBeenCalledWith({
      hubspotDealId: "deal-1",
      propName: PIBI_ORDERED_TEXT_PROP,
      measureNames: ["CWI", "Loft insulation"],
    });

    expect(deps.stampPushedAt).toHaveBeenCalledWith([10n, 11n]);
  });

  it("uses now() when orderedAt is omitted", async () => {
    const before = new Date();
    const deps = makeDeps();

    await createPibiRequests({
      dealId: "deal-2",
      portfolioId: 1n,
      measureNames: ["ASHP"],
      userId: 1n,
      deps,
    });

    const callArg = (deps.runCreateTx as ReturnType<typeof vi.fn>).mock.calls[0][0] as {
      orderedAt: Date;
    };
    expect(callArg.orderedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
  });
});

describe("createPibiRequests — validation", () => {
  it("returns ok=false immediately when measureNames is empty", async () => {
    const deps = makeDeps();

    const result = await createPibiRequests({
      dealId: "deal-3",
      portfolioId: 1n,
      measureNames: [],
      userId: 1n,
      deps,
    });

    expect(result).toEqual({ ok: false, error: "measureNames must not be empty" });
    expect(deps.runCreateTx).not.toHaveBeenCalled();
    expect(deps.syncMeasuresField).not.toHaveBeenCalled();
  });
});

describe("createPibiRequests — DB failure", () => {
  it("returns ok=false, skips HubSpot when tx throws", async () => {
    const deps = makeDeps({ txError: new Error("insert failed") });

    const result = await createPibiRequests({
      dealId: "deal-x",
      portfolioId: 1n,
      measureNames: ["EWI"],
      userId: 1n,
      deps,
    });

    expect(result).toEqual({ ok: false, error: "insert failed" });
    expect(deps.syncMeasuresField).not.toHaveBeenCalled();
    expect(deps.stampPushedAt).not.toHaveBeenCalled();
  });
});

describe("createPibiRequests — HubSpot failure", () => {
  it("returns ok=true with hubspotSync=failed, does NOT stamp pushed_at", async () => {
    const deps = makeDeps({
      txResult: { insertedRowIds: [20n], allMeasureNames: ["Solar PV"] },
      syncResult: { ok: false, error: "hubspot 503" },
    });

    const result = await createPibiRequests({
      dealId: "deal-h",
      portfolioId: 1n,
      measureNames: ["Solar PV"],
      userId: 2n,
      deps,
    });

    expect(result).toMatchObject({
      ok: true,
      insertedRowIds: [20n],
      hubspotSync: "failed",
      hubspotError: "hubspot 503",
    });

    expect(deps.runCreateTx).toHaveBeenCalledTimes(1);
    expect(deps.stampPushedAt).not.toHaveBeenCalled();
  });
});
