import { expect, test } from "bun:test";
import { createContext } from "@wrnexus/core";
import {
  getOriginalRequestMethod,
  getOriginalRequestOrigin,
  getOriginalRequestPath,
  getOriginalRequestUrl,
  redirectToLogin,
  stableStringify,
} from "../src/index.ts";

test("derives the shared workspace root domain from configured public app origins", async () => {
  const previous = process.env.WRNEXUS_WORKSPACE_ORIGINS;
  process.env.WRNEXUS_WORKSPACE_ORIGINS = JSON.stringify({
    admin: "https://admin.staging.example.com",
    citizen: "https://citizen.staging.example.com",
    sso: "https://sso.staging.example.com",
  });
  try {
    const { workspaceRootDomain } = await import("../src/workspace.ts");
    expect(workspaceRootDomain()).toBe("staging.example.com");
  } finally {
    if (previous === undefined) delete process.env.WRNEXUS_WORKSPACE_ORIGINS;
    else process.env.WRNEXUS_WORKSPACE_ORIGINS = previous;
  }
});

function context(url: string, headers: HeadersInit = {}) {
  const parsed = new URL(url);
  return createContext(new Request(parsed, { headers }), parsed);
}

test("uses the direct context URL when no gateway headers exist", () => {
  const ctx = context("https://app.example.test/account?tab=security");

  expect(getOriginalRequestUrl(ctx).href).toBe("https://app.example.test/account?tab=security");
  expect(getOriginalRequestOrigin(ctx)).toBe("https://app.example.test");
  expect(getOriginalRequestPath(ctx)).toBe("/account?tab=security");
  expect(getOriginalRequestMethod(ctx)).toBe("GET");
});

test("reconstructs an allowed original gateway URL", () => {
  const ctx = context("http://sso.localhost:3000/api/verify", {
    "x-forwarded-host": "admin.localhost:3000",
    "x-forwarded-proto": "http",
    "x-original-method": "GET",
    "x-original-uri": "/users?page=2",
  });

  const url = getOriginalRequestUrl(ctx, { allowedHosts: ["admin.localhost:3000"] });
  expect(url.href).toBe("http://admin.localhost:3000/users?page=2");
  expect(getOriginalRequestMethod(ctx)).toBe("GET");
});

test("keeps the protected app URL when the SSO verifier crosses the gateway again", () => {
  const ctx = context("http://sso.localhost:3000/api/verify", {
    "x-forwarded-host": "sso.localhost:3000",
    "x-forwarded-proto": "http",
    "x-original-host": "admin.localhost:3000",
    "x-original-proto": "http",
    "x-original-method": "GET",
    "x-original-uri": "/settings?tab=security",
  });

  const response = redirectToLogin(ctx, "/login", {
    allowedHosts: ["admin.localhost:3000"],
  });
  const location = new URL(response.headers.get("location")!);

  expect(location.origin).toBe("http://sso.localhost:3000");
  expect(location.pathname).toBe("/login");
  expect(location.searchParams.get("returnTo")).toBe(
    "http://admin.localhost:3000/settings?tab=security",
  );
});

test("rejects untrusted hosts and unsafe request paths", () => {
  const untrusted = context("http://sso.localhost/api/verify", {
    "x-forwarded-host": "evil.example",
    "x-original-uri": "/",
  });
  const unsafePath = context("http://sso.localhost/api/verify", {
    "x-forwarded-host": "admin.localhost",
    "x-original-uri": "//evil.example/steal",
  });

  expect(() => getOriginalRequestUrl(untrusted)).toThrow("Untrusted forwarded host");
  expect(() => getOriginalRequestUrl(unsafePath, { allowedHosts: ["admin.localhost"] })).toThrow(
    "absolute request path",
  );
});

test("creates a safe login redirect with an encoded returnTo URL", () => {
  const ctx = context("http://sso.localhost:3000/api/verify", {
    "x-forwarded-host": "admin.localhost:3000",
    "x-forwarded-proto": "http",
    "x-original-uri": "/reports?range=week",
  });

  const response = redirectToLogin(ctx, "/login", {
    allowedHosts: new Set(["admin.localhost:3000"]),
  });
  const location = new URL(response.headers.get("location")!);

  expect(response.status).toBe(302);
  expect(location.origin).toBe("http://sso.localhost:3000");
  expect(location.pathname).toBe("/login");
  expect(location.searchParams.get("returnTo")).toBe(
    "http://admin.localhost:3000/reports?range=week",
  );
});

test("relative login redirects use the configured public app origin", () => {
  const previous = process.env.WRNEXUS_APP_ORIGIN;
  process.env.WRNEXUS_APP_ORIGIN = "https://sso.example.test";
  try {
    const ctx = context("http://127.0.0.1:3002/api/verify", {
      "x-original-host": "admin.example.test",
      "x-original-proto": "https",
      "x-original-uri": "/settings",
    });
    const response = redirectToLogin(ctx, "/sign-in", {
      allowedHosts: ["admin.example.test"],
    });
    const location = new URL(response.headers.get("location")!);

    expect(location.origin).toBe("https://sso.example.test");
    expect(location.pathname).toBe("/sign-in");
    expect(location.searchParams.get("returnTo")).toBe("https://admin.example.test/settings");
  } finally {
    if (previous === undefined) delete process.env.WRNEXUS_APP_ORIGIN;
    else process.env.WRNEXUS_APP_ORIGIN = previous;
  }
});

test("supports an allowed-host callback and custom response options", () => {
  const ctx = context("https://login.example.test/api/verify", {
    "x-forwarded-host": "reports.example.test",
    "x-forwarded-proto": "https",
    "x-original-uri": "/",
  });
  const response = redirectToLogin(ctx, "https://login.example.test/sign-in?tenant=acme", {
    allowedHosts: (host) => host.endsWith(".example.test"),
    returnToParam: "next",
    status: 303,
  });
  const location = new URL(response.headers.get("location")!);

  expect(response.status).toBe(303);
  expect(location.searchParams.get("tenant")).toBe("acme");
  expect(location.searchParams.get("next")).toBe("https://reports.example.test/");
});

test("stableStringify permits repeated references but rejects cycles", () => {
  const shared = { value: 1 };
  expect(stableStringify({ second: shared, first: shared })).toBe(
    '{"first":{"value":1},"second":{"value":1}}',
  );
  const circular: { self?: unknown } = {};
  circular.self = circular;
  expect(() => stableStringify(circular)).toThrow("circular");
});
