/* @vitest-environment node */

import { describe, it, expect } from "vitest";
import {
	LOGIN_REASONS,
	isKnownLoginReason,
	sanitizeNext,
	buildLoginUrl,
	parseLoginParams,
} from "./authRedirect.js";

describe("lib/frontend/authRedirect", () => {
	describe("isKnownLoginReason", () => {
		it("accepts only the explicit known reasons", () => {
			expect(isKnownLoginReason(LOGIN_REASONS.EXPIRED)).toBe(true);
			expect(isKnownLoginReason(LOGIN_REASONS.LOGGED_OUT)).toBe(true);

			expect(isKnownLoginReason("something-else")).toBe(false);
			expect(isKnownLoginReason("")).toBe(false);
			expect(isKnownLoginReason(null)).toBe(false);
			expect(isKnownLoginReason(undefined)).toBe(false);
		});
	});

	describe("sanitizeNext", () => {
		it("accepts internal paths that start with a single slash", () => {
			expect(sanitizeNext("/")).toBe("/");
			expect(sanitizeNext("/NL01")).toBe("/NL01");
			expect(sanitizeNext("/NL01/2025/12/31")).toBe("/NL01/2025/12/31");
			expect(sanitizeNext("/NL01?x=1#hash")).toBe("/NL01?x=1#hash");
		});

		it("trims whitespace", () => {
			expect(sanitizeNext("   /NL01   ")).toBe("/NL01");
		});

		it("rejects empty or non-string input", () => {
			expect(sanitizeNext("")).toBe(null);
			expect(sanitizeNext("   ")).toBe(null);
			expect(sanitizeNext(null)).toBe(null);
			expect(sanitizeNext(undefined)).toBe(null);
			expect(sanitizeNext(123)).toBe(null);
		});

		it("rejects paths that do not start with '/'", () => {
			expect(sanitizeNext("NL01")).toBe(null);
			expect(sanitizeNext("login")).toBe(null);
			expect(sanitizeNext("http://evil.com")).toBe(null);
		});

		it("rejects protocol-relative URLs and backslashes", () => {
			expect(sanitizeNext("//evil.com")).toBe(null);

			// Backslashes can lead to confusing path interpretations.
			expect(sanitizeNext("/\\evil")).toBe(null);
			expect(sanitizeNext("/NL01\\2025")).toBe(null);
		});
	});

	describe("buildLoginUrl", () => {
		it("returns plain /login when no params are provided", () => {
			expect(buildLoginUrl()).toBe("/login");
			expect(buildLoginUrl({})).toBe("/login");
		});

		it("adds reason when valid", () => {
			expect(buildLoginUrl({ reason: "expired" })).toBe(
				"/login?reason=expired"
			);
		});

		it("adds next when valid", () => {
			expect(buildLoginUrl({ next: "/NL01" })).toBe("/login?next=%2FNL01");
		});

		it("adds reason first, then next (stable ordering)", () => {
			expect(buildLoginUrl({ reason: "expired", next: "/NL01" })).toBe(
				"/login?reason=expired&next=%2FNL01"
			);
		});

		it("ignores invalid reason and unsafe next", () => {
			// Invalid reason is dropped; unsafe next is dropped.
			expect(buildLoginUrl({ reason: "nope", next: "//evil.com" })).toBe(
				"/login"
			);
		});
	});

	describe("parseLoginParams", () => {
		it("parses from URLSearchParams (client-side style)", () => {
			const sp = new URLSearchParams({
				reason: "expired",
				next: "/NL01/2025",
			});

			expect(parseLoginParams(sp)).toEqual({
				reason: "expired",
				next: "/NL01/2025",
			});
		});

		it("parses from plain object (Next.js server searchParams style)", () => {
			const sp = {
				reason: "logged-out",
				next: "/NL02",
			};

			expect(parseLoginParams(sp)).toEqual({
				reason: "logged-out",
				next: "/NL02",
			});
		});

		it("normalizes unknown reason to null and sanitizes next", () => {
			const sp = new URLSearchParams({
				reason: "unknown",
				next: "//evil.com",
			});

			expect(parseLoginParams(sp)).toEqual({
				reason: null,
				next: null,
			});
		});

		it("handles missing params", () => {
			const sp = new URLSearchParams();
			expect(parseLoginParams(sp)).toEqual({ reason: null, next: null });
		});
	});
});
