import { baseWindowConfig } from "../../../vitest.setup";
import {
  assignAppRelativeLocation,
  createLoginRedirectUrl,
  createRedirectUrlWithReturn,
  getReturnUrl,
  isServerOwnedPath,
  sanitizeUrl,
} from "../routingUtils";

const maliciousURLS = [
  "https://google.com",
  "http://badsite.io",
  "badsite.io",
  "//bin",
  // Backslash are interpreted as slash by browsers
  "/\\bin",
  "\\\\bin",
];

const validURLs = [
  "/account",
  "/organizations/QWNjb3VudE9yZ2FuaXphdGlvbjoxNTg=/spaces/U3BhY2U6MTU4",
];
describe("routingUtils", () => {
  let windowSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    windowSpy = vi.spyOn(window, "location", "get");
  });

  afterEach(() => {
    vi.restoreAllMocks();
    vi.resetAllMocks();
    Object.defineProperty(window, "Config", {
      value: baseWindowConfig,
      writable: true,
    });
  });
  describe("isServerOwnedPath", () => {
    test.each([
      "/oauth2/authorize",
      "/oauth2/authorize?client_id=abc&state=xyz",
      "/oauth2/authorize/",
    ])("%s is server-owned - requires a full document load", (url) => {
      expect(isServerOwnedPath(url)).toBe(true);
    });

    test.each([
      "/",
      "/account",
      "/oauth2/consent",
      "/oauth2/authorized",
      "/login?returnUrl=%2Foauth2%2Fauthorize",
    ])("%s is an SPA route - handled by the router", (url) => {
      expect(isServerOwnedPath(url)).toBe(false);
    });
  });

  describe("assignAppRelativeLocation", () => {
    it("resolves the path against the current origin", () => {
      const assign = vi.fn();
      windowSpy.mockReturnValue({
        origin: "http://localhost:6006",
        assign,
      });
      assignAppRelativeLocation("/oauth2/authorize?client_id=abc");
      expect(assign).toHaveBeenCalledWith(
        "http://localhost:6006/oauth2/authorize?client_id=abc"
      );
    });

    test.each(["javascript:alert(1)", "https://evil.example/phish"])(
      "%s cannot navigate off the current origin",
      (path) => {
        const assign = vi.fn();
        windowSpy.mockReturnValue({
          origin: "http://localhost:6006",
          assign,
        });
        assignAppRelativeLocation(path);
        expect(assign).not.toHaveBeenCalled();
      }
    );
  });

  describe("sanitizeRedirectUrl", () => {
    test.each(maliciousURLS)("%s is invalid - route to root", (url) => {
      expect(sanitizeUrl(url)).toEqual("/");
    });

    test.each(validURLs)("%s is valid - route to passed value", (url) => {
      expect(sanitizeUrl(url)).toEqual(url);
    });
  });

  describe("getReturnUrl", () => {
    it("should return the returnUrl from the query string", () => {
      const returnUrl = "/account";
      windowSpy.mockReturnValueOnce({
        href: `http://localhost:6006?returnUrl=${returnUrl}`,
      });
      expect(getReturnUrl()).toEqual(returnUrl);
    });

    it("should return the returnUrl from the query string with a query param", () => {
      const returnUrl = "/account";
      windowSpy.mockReturnValueOnce({
        href: `http://localhost:6006?returnUrl=${returnUrl}&test=true`,
      });
      expect(getReturnUrl()).toEqual(returnUrl);
    });

    it("should return the returnUrl from the query string with a query param and hash", () => {
      const returnUrl = "/account";
      windowSpy.mockReturnValueOnce({
        href: `http://localhost:6006?returnUrl=${returnUrl}&test=true#test`,
      });
      expect(getReturnUrl()).toEqual(returnUrl);
    });

    it("should sanitize the returnUrl from the query string", () => {
      const returnUrl = "https://google.com";
      windowSpy.mockReturnValueOnce({
        href: `http://localhost:6006?returnUrl=${returnUrl}`,
      });
      expect(getReturnUrl()).toEqual("/");
    });
  });

  describe("createRedirectUrlWithReturn", () => {
    it("should append the returnUrl to the path", () => {
      const path = "/login";
      const returnUrl = "/projects/QWNjb3VudE9yZGluaXphdGlvbjoxNTg=";
      windowSpy.mockReturnValueOnce({
        href: `http://localhost:6006?returnUrl=${returnUrl}`,
      });
      expect(createRedirectUrlWithReturn({ path })).toEqual(
        `${path}?returnUrl=${encodeURIComponent(returnUrl)}`
      );
    });

    it("should just return the path if there is no returnUrl param", () => {
      const path = "/account?test=true";
      expect(createRedirectUrlWithReturn({ path })).toEqual(path);
    });

    it("should support additional params", () => {
      const path = "/login";
      expect(
        createRedirectUrlWithReturn({
          path,
          searchParams: {
            message: "Password has been reset.",
          },
        })
      ).toEqual(`${path}?message=Password+has+been+reset.`);
    });
  });

  describe("createLoginRedirectUrl", () => {
    test.each([
      {
        description:
          "should redirect to login with returnUrl query param when pathname is empty",
        pathname: "",
        search: "",
        basename: undefined,
        expected: "/login?returnUrl=%2F",
      },
      {
        description:
          "should redirect to login with returnUrl query param when pathname is /",
        pathname: "/",
        search: "",
        basename: "",
        expected: "/login?returnUrl=%2F",
      },
      {
        description:
          "should return the returnUrl query param with the current pathname",
        pathname: "/account",
        search: "",
        basename: "/",
        expected: "/login?returnUrl=%2Faccount",
      },
      {
        description:
          "should return the returnUrl query param with the current pathname and search params",
        pathname: "/account",
        search: "?test=true",
        basename: "",
        expected: "/login?returnUrl=%2Faccount%3Ftest%3Dtrue",
      },
      {
        description:
          "should redirect to basename login with returnUrl query param with the current pathname",
        pathname: "/basename/account",
        search: "",
        basename: "/basename",
        expected: "/basename/login?returnUrl=%2Faccount",
      },
      {
        description:
          "should redirect to basename login with returnUrl query param with the current pathname and search params",
        pathname: "/basename/account",
        search: "?test=true",
        basename: "/basename",
        expected: "/basename/login?returnUrl=%2Faccount%3Ftest%3Dtrue",
      },
      {
        description:
          "should redirect to basename login when pathname does not start with basename",
        pathname: "/doesNotStartWithBasename/account",
        search: "?test=true",
        basename: "/basename",
        expected: "/basename/login",
      },
      {
        description:
          "should redirect to basename login when pathname starts with basename but does not match",
        pathname: "/basenameAndExtra/account",
        search: "?test=true",
        basename: "/basename",
        expected: "/basename/login",
      },
    ])("$description", ({ pathname, search, basename, expected }) => {
      windowSpy.mockReturnValue({
        pathname,
        origin: "http://127.0.0.1:6006",
        search,
      });
      Object.defineProperty(window, "Config", {
        value: { ...baseWindowConfig, basename },
      });
      expect(createLoginRedirectUrl()).toEqual(expected);
    });
  });
});
