/**
 * Auth redirect helpers (RHL-020).
 *
 * Why this file exists:
 * - We want a clean, testable place for all "login redirect" rules.
 * - UI components should not re-implement URL parsing or "next" sanitization.
 * - This prevents bugs, keeps behavior consistent, and avoids open-redirect issues.
 *
 * This module contains only pure functions (no DOM, no Next runtime required).
 */

import { loginPath } from "@/lib/frontend/routes";

/**
 * Allowed reasons for /login messages.
 * Anything else is treated as "unknown" and ignored.
 *
 * Keep this list small and explicit.
 */
export const LOGIN_REASONS = Object.freeze({
	EXPIRED: "expired",
	LOGGED_OUT: "logged-out",
});

/**
 * Check if a given string is one of our known login reasons.
 *
 * @param {unknown} value
 * @returns {value is string}
 */
export function isKnownLoginReason(value) {
	return value === LOGIN_REASONS.EXPIRED || value === LOGIN_REASONS.LOGGED_OUT;
}

/**
 * Sanitize a "next" path to prevent open redirects.
 *
 * Security goal:
 * - Only allow internal paths like "/NL01/2025/12/31?x=1".
 * - Reject absolute URLs ("https://evil.com") and protocol-relative URLs ("//evil.com").
 *
 * Rule set (intentionally strict):
 * - Must be a non-empty string after trimming.
 * - Must start with exactly one "/" (i.e. "/" is ok, but "//" is not).
 * - Must not contain backslashes (avoid weird path interpretations on some platforms).
 *
 * Note:
 * - URLSearchParams returns *decoded* strings.
 *   Example: next=%2F%2Fevil.com becomes "//evil.com" -> rejected by our rules.
 *
 * @param {unknown} nextValue
 * @returns {string|null} safe internal path or null when invalid/unsafe
 */
export function sanitizeNext(nextValue) {
	if (typeof nextValue !== "string") return null;

	const trimmed = nextValue.trim();
	if (!trimmed) return null;

	// Must be an internal path.
	if (!trimmed.startsWith("/")) return null;

	// Reject protocol-relative URLs.
	if (trimmed.startsWith("//")) return null;

	// Reject backslashes (avoid ambiguous interpretations).
	if (trimmed.includes("\\")) return null;

	return trimmed;
}

/**
 * Build a /login URL with optional reason + next.
 *
 * Design:
 * - We only add parameters if they are present and valid.
 * - We keep a stable parameter ordering for predictable tests and debugging:
 *   - reason first, then next.
 *
 * @param {{ reason?: string|null, next?: string|null }} input
 * @returns {string} e.g. "/login?reason=expired&next=%2FNL01"
 */
export function buildLoginUrl({ reason, next } = {}) {
	const base = loginPath();

	const safeReason = isKnownLoginReason(reason) ? reason : null;
	const safeNext = sanitizeNext(next);

	const params = new URLSearchParams();

	// Stable param insertion order (important for tests and log readability).
	if (safeReason) params.set("reason", safeReason);
	if (safeNext) params.set("next", safeNext);

	const qs = params.toString();
	return qs ? `${base}?${qs}` : base;
}

/**
 * Read a query parameter from either:
 * - URLSearchParams (client-side `useSearchParams()`)
 * - Next.js server `searchParams` object (plain object with string or string[])
 *
 * This keeps our parsing logic independent from where it is called.
 *
 * @param {any} searchParams
 * @param {string} key
 * @returns {string|null}
 */
function readParam(searchParams, key) {
	if (!searchParams) return null;

	// URLSearchParams-like object: has .get()
	if (typeof searchParams.get === "function") {
		const value = searchParams.get(key);
		return typeof value === "string" ? value : null;
	}

	// Next.js server searchParams: plain object
	const raw = searchParams[key];

	if (Array.isArray(raw)) {
		// If multiple values exist, we take the first one.
		return typeof raw[0] === "string" ? raw[0] : null;
	}

	return typeof raw === "string" ? raw : null;
}

/**
 * Parse /login params (reason + next) in a safe and normalized way.
 *
 * Output contract:
 * - reason: "expired" | "logged-out" | null
 * - next: sanitized internal path or null
 *
 * @param {URLSearchParams|Record<string, any>|any} searchParams
 * @returns {{ reason: string|null, next: string|null }}
 */
export function parseLoginParams(searchParams) {
	const rawReason = readParam(searchParams, "reason");
	const rawNext = readParam(searchParams, "next");

	const reason = isKnownLoginReason(rawReason) ? rawReason : null;
	const next = sanitizeNext(rawNext);

	return { reason, next };
}
