import { describe, expect, it, vi } from "vitest";
import type { BrowserRouteContext } from "../server-context.js";

vi.mock("./index.js", () => {
  return {
    registerBrowserRoutes(app: { get: (path: string, handler: unknown) => void }) {
      app.get(
        "/slow",
        async (req: { signal?: AbortSignal }, res: { json: (body: unknown) => void }) => {
          const signal = req.signal;
          await new Promise<void>((resolve, reject) => {
            if (signal?.aborted) {
              reject(signal.reason ?? new Error("aborted"));
              return;
            }
            const onAbort = () => reject(signal?.reason ?? new Error("aborted"));
            signal?.addEventListener("abort", onAbort, { once: true });
            queueMicrotask(() => {
              signal?.removeEventListener("abort", onAbort);
              resolve();
            });
          });
          res.json({ ok: true });
        },
      );
      app.get(
        "/echo/:id",
        async (
          req: { params?: Record<string, string> },
          res: { json: (body: unknown) => void },
        ) => {
          res.json({ id: req.params?.id ?? null });
        },
      );
    },
  };
});

describe("browser route dispatcher (abort)", () => {
  it("propagates AbortSignal and lets handlers observe abort", async () => {
    const { createBrowserRouteDispatcher } = await import("./dispatcher.js");
    const dispatcher = createBrowserRouteDispatcher({} as BrowserRouteContext);

    const ctrl = new AbortController();
    const promise = dispatcher.dispatch({
      method: "GET",
      path: "/slow",
      signal: ctrl.signal,
    });

    ctrl.abort(new Error("timed out"));

    await expect(promise).resolves.toMatchObject({
      status: 500,
      body: { error: expect.stringContaining("timed out") },
    });
  });

  it("returns 400 for malformed percent-encoding in route params", async () => {
    const { createBrowserRouteDispatcher } = await import("./dispatcher.js");
    const dispatcher = createBrowserRouteDispatcher({} as BrowserRouteContext);

    await expect(
      dispatcher.dispatch({
        method: "GET",
        path: "/echo/%E0%A4%A",
      }),
    ).resolves.toMatchObject({
      status: 400,
      body: { error: expect.stringContaining("invalid path parameter encoding") },
    });
  });
});
