import { describe, expect, test } from "bun:test";
import {
  RPC_ERROR_CODES,
  failure,
  retryingTransport,
  success,
  type Transport,
} from "../src/index.ts";

function failingTransport(): { transport: Transport; calls: () => number } {
  let count = 0;
  return {
    transport: {
      async call() {
        count++;
        return failure(RPC_ERROR_CODES.transport, "down");
      },
    },
    calls: () => count,
  };
}

describe("retryingTransport", () => {
  test("retries only declared idempotent calls", async () => {
    const retryable = failingTransport();
    const write = failingTransport();
    const options = { retries: 2, backoffMs: 0 };
    await retryingTransport(retryable.transport, options).call(
      { app: "billing", service: "invoice", procedure: "get" },
      {},
      { idempotent: true },
    );
    await retryingTransport(write.transport, options).call(
      { app: "billing", service: "invoice", procedure: "create" },
      {},
      { idempotent: false },
    );
    expect(retryable.calls()).toBe(3);
    expect(write.calls()).toBe(1);
  });

  test("opens a circuit after repeated exhausted failures and recovers after cooldown", async () => {
    let clock = 0;
    let calls = 0;
    const base: Transport = {
      async call() {
        calls++;
        return calls < 3 ? failure(RPC_ERROR_CODES.transport, "down") : success("ok");
      },
    };
    const transport = retryingTransport(base, {
      retries: 0,
      circuitFailureThreshold: 2,
      circuitCooldownMs: 10,
      now: () => clock,
    });
    const target = { app: "billing", service: "invoice", procedure: "get" };
    await transport.call(target, {}, { idempotent: true });
    await transport.call(target, {}, { idempotent: true });
    expect(await transport.call(target, {}, { idempotent: true })).toMatchObject({
      ok: false,
      code: RPC_ERROR_CODES.transport,
    });
    expect(calls).toBe(2);
    clock = 11;
    expect(await transport.call(target, {}, { idempotent: true })).toEqual(success("ok"));
  });
});
