import { describe, it, expect, expectTypeOf } from "vitest";
import { withRateLimit } from "@/rate-limit/with-rate-limit";
import {
  isRateLimited,
  isInstrumented,
  isCaptured,
} from "@/conformance/brand-runtime";
import { attachBrand } from "@/conformance/brand-runtime";
import type { RateLimited } from "@/conformance/brands";
import { NoopRateLimit } from "@/rate-limit/noop-rate-limit";

function makeRateLimit() {
  return new NoopRateLimit();
}

describe("withRateLimit — brand", () => {
  it("returns a RateLimited<F> type", () => {
    const rateLimit = makeRateLimit();
    const fn = async (a: number) => a + 1;
    const wrapped = withRateLimit(rateLimit, fn);
    expectTypeOf(wrapped).toMatchTypeOf<RateLimited<typeof fn>>();
  });

  it("attaches __rateLimited as a non-enumerable property", () => {
    const rateLimit = makeRateLimit();
    const wrapped = withRateLimit(rateLimit, async (x: number) => x + 1);
    expect(isRateLimited(wrapped)).toBe(true);
    expect(Object.keys(wrapped)).not.toContain("__rateLimited");
  });
});

describe("withRateLimit — factory passthrough", () => {
  it("forwards arguments and return value unchanged on success", async () => {
    const rateLimit = makeRateLimit();
    const fn = async (a: number, b: number) => a + b;
    const wrapped = withRateLimit(rateLimit, fn);
    await expect(wrapped(3, 4)).resolves.toBe(7);
  });

  it("re-throws errors from the inner function", async () => {
    const rateLimit = makeRateLimit();
    const err = new Error("inner failure");
    const fn = async () => {
      throw err;
    };
    const wrapped = withRateLimit(rateLimit, fn);
    await expect(wrapped()).rejects.toBe(err);
  });
});

describe("withRateLimit — does not attach other brands", () => {
  it("does not attach __instrumented or __captured", () => {
    const rateLimit = makeRateLimit();
    const wrapped = withRateLimit(rateLimit, async (x: number) => x);
    expect(isInstrumented(wrapped)).toBe(false);
    expect(isCaptured(wrapped)).toBe(false);
  });
});

describe("withRateLimit — composition with other wrappers", () => {
  it("composed result carries __rateLimited when withRateLimit is innermost", () => {
    const rateLimit = makeRateLimit();
    const raw = async (x: number) => x * 2;
    const rateLimited = withRateLimit(rateLimit, raw);

    // Simulate outer wrapper propagating __rateLimited
    const outer: typeof raw = (...args) => rateLimited(...args);
    if (
      (rateLimited as unknown as Record<string, unknown>)["__rateLimited"] ===
      true
    ) {
      attachBrand(outer, "__rateLimited");
    }
    expect(isRateLimited(outer)).toBe(true);
  });

  it("original fn reference is not mutated", () => {
    const rateLimit = makeRateLimit();
    const fn = async (x: number) => x;
    const wrapped = withRateLimit(rateLimit, fn);
    expect(wrapped).not.toBe(fn);
    expect(isRateLimited(fn)).toBe(false);
    expect(isRateLimited(wrapped)).toBe(true);
  });
});
