import { expect, test } from "bun:test";
import {
  checkPerformanceBudgets,
  createContext,
  createTracer,
  dedupe,
  defineAction,
  defineEndpoint,
  defineFeatureFlags,
  defineLoader,
  tenantFromSubdomain,
  assertTenantAccess,
  createTenantDirectory,
  tenantKey,
  tracingMiddleware,
} from "../src/index.ts";

function context(url = "https://acme.example.com/dashboard") {
  return createContext(new Request(url), new URL(url));
}

test("typed endpoints validate authentication and preserve a stable JSON envelope", async () => {
  const endpoint = defineEndpoint<{ value: number }, { doubled: number }>({
    auth: "required",
    input: {
      parse(input) {
        const value = Number((input as { value?: unknown })?.value);
        if (!Number.isFinite(value)) throw new Error("invalid");
        return { value };
      },
    },
    handler: ({ value }) => ({ doubled: value * 2 }),
  });

  expect((await endpoint(context(), { value: 4 })).status).toBe(401);
  const authenticated = context();
  authenticated.user = { id: "user-1" };
  expect(await (await endpoint(authenticated, { value: 4 })).json()).toEqual({
    data: { doubled: 8 },
  });
});

test("tenant boundaries, memberships, workspaces, quotas, and audit events fail closed", async () => {
  const events: string[] = [];
  const directory = createTenantDirectory({
    audit: (event) => {
      events.push(event.action);
    },
    now: () => 10,
  });
  await directory.addMembership({ tenantId: "acme", userId: "u1", workspaceIds: ["north"] });
  expect(await directory.switchWorkspace("acme", "u1", "north")).toEqual({
    tenantId: "acme",
    workspaceId: "north",
  });
  await expect(directory.switchWorkspace("acme", "u1", "south")).rejects.toThrow(
    "WRN-TENANT-WORKSPACE-DENIED",
  );
  directory.setQuota("acme", "storage", 100);
  expect(() => directory.enforceQuota("acme", "storage", 90, 11)).toThrow("WRN-TENANT-QUOTA");
  expect(() => assertTenantAccess({ id: "acme" }, { tenantId: "other" })).toThrow(
    "WRN-TENANT-CROSS-ACCESS",
  );
  expect(tenantKey("acme", "cache", 1)).toBe("tenant:acme:cache:1");
  expect(events).toEqual(["membership.added", "workspace.switched"]);
});

test("loaders, actions, and request-local dedupe remain framework-agnostic", async () => {
  let calls = 0;
  const loader = defineLoader({ load: async () => ({ ready: true }) });
  const action = defineAction<{ name: string }, string>({ run: async (input) => input.name });
  const ctx = context();
  const first = dedupe(ctx, "profile", async () => ++calls);
  const second = dedupe(ctx, "profile", async () => ++calls);

  expect(await loader(ctx)).toEqual({ ready: true });
  expect(await action({ name: "Ajay" }, ctx)).toBe("Ajay");
  expect(await Promise.all([first, second])).toEqual([1, 1]);
  expect(calls).toBe(1);
});

test("feature flags, tenant resolution, budgets, and tracing compose", async () => {
  const ctx = context();
  const resolveTenant = tenantFromSubdomain(async (slug) => ({ id: slug, slug }), ["example.com"]);
  expect(await resolveTenant(ctx)).toEqual({ id: "acme", slug: "acme" });

  const flags = defineFeatureFlags({ dashboardV2: true, seats: 25 });
  expect(await flags.enabled("dashboardV2", ctx)).toBe(true);
  expect(await flags.get("seats", ctx)).toBe(25);

  expect(checkPerformanceBudgets({ routeJsBytes: 100 }, { routeJsBytes: 130 })).toEqual([
    { metric: "routeJsBytes", budget: 100, actual: 130, overBy: 30 },
  ]);

  const tracer = createTracer(() => 10);
  const middleware = tracingMiddleware(() => tracer, { serverTiming: true });
  const response = await middleware(ctx, () => new Response("ok"));
  expect(response.headers.get("server-timing")).toContain("http.request");
});
