/* @vitest-environment node */

import { describe, it, expect } from "vitest";
import {
	SEARCH_SCOPE,
	SEARCH_LIMITS,
	DEFAULT_SEARCH_LIMIT,
	parseBranchesCsv,
	serializeBranchesCsv,
	parseSearchUrlState,
	serializeSearchUrlState,
} from "./urlState.js";

describe("lib/frontend/search/urlState", () => {
	describe("parseBranchesCsv / serializeBranchesCsv", () => {
		it("parses CSV into a unique, trimmed, sorted list", () => {
			expect(parseBranchesCsv(" NL20, NL06 ,NL06,, ")).toEqual([
				"NL06",
				"NL20",
			]);
		});

		it("normalizes to uppercase (fail-open) and sorts deterministically", () => {
			expect(parseBranchesCsv("nl20, NL06")).toEqual(["NL06", "NL20"]);
		});

		it("serializes branches into CSV (sorted + deduped)", () => {
			expect(serializeBranchesCsv(["NL20", " NL06 ", "NL20"])).toBe(
				"NL06,NL20"
			);
		});

		it("returns null when serializing empty branches", () => {
			expect(serializeBranchesCsv([])).toBe(null);
			expect(serializeBranchesCsv(null)).toBe(null);
		});
	});

	describe("parseSearchUrlState", () => {
		it("defaults to SINGLE with routeBranch when no params are present", () => {
			const sp = new URLSearchParams();
			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });

			expect(state).toEqual({
				q: null,
				scope: SEARCH_SCOPE.SINGLE,
				branch: "NL01",
				branches: [],
				limit: DEFAULT_SEARCH_LIMIT,
				from: null,
				to: null,
			});
		});

		it("parses SINGLE with explicit branch param (legacy URL)", () => {
			const sp = new URLSearchParams({ q: " test ", branch: "nl02" });
			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });

			expect(state.scope).toBe(SEARCH_SCOPE.SINGLE);
			expect(state.q).toBe("test");
			expect(state.branch).toBe("NL02");
			expect(state.branches).toEqual([]);
			expect(SEARCH_LIMITS.includes(state.limit)).toBe(true);
		});

		it("parses ALL when scope=all is set (highest precedence)", () => {
			const sp = new URLSearchParams({
				q: "x",
				scope: "all",
				branch: "NL01",
				branches: "NL06,NL20",
				limit: "200",
			});

			const state = parseSearchUrlState(sp, { routeBranch: "NL99" });

			expect(state).toEqual({
				q: "x",
				scope: SEARCH_SCOPE.ALL,
				branch: null,
				branches: [],
				limit: 200,
				from: null,
				to: null,
			});
		});

		it("parses MULTI when scope=multi is set", () => {
			const sp = new URLSearchParams({
				q: " reifen ",
				scope: "multi",
				branches: "NL06, NL20, NL06",
				limit: "50",
			});

			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });

			expect(state.scope).toBe(SEARCH_SCOPE.MULTI);
			expect(state.q).toBe("reifen");
			expect(state.branch).toBe(null);
			expect(state.branches).toEqual(["NL06", "NL20"]);
			expect(state.limit).toBe(50);
		});

		it("parses MULTI when branches=... is present even without scope", () => {
			const sp = new URLSearchParams({
				q: "x",
				branches: "NL20,NL06",
			});

			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });

			expect(state.scope).toBe(SEARCH_SCOPE.MULTI);
			expect(state.branches).toEqual(["NL06", "NL20"]);
			expect(state.limit).toBe(DEFAULT_SEARCH_LIMIT);
		});

		it("keeps from/to when provided", () => {
			const sp = new URLSearchParams({
				q: "x",
				branch: "NL01",
				from: "2025-12-01",
				to: "2025-12-31",
				limit: "200",
			});

			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });

			expect(state.from).toBe("2025-12-01");
			expect(state.to).toBe("2025-12-31");
			expect(state.limit).toBe(200);
		});

		it("falls back to default limit for invalid values", () => {
			const sp = new URLSearchParams({ q: "x", branch: "NL01", limit: "999" });
			const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
			expect(state.limit).toBe(DEFAULT_SEARCH_LIMIT);
		});
	});

	describe("serializeSearchUrlState", () => {
		it("serializes SINGLE as q only (path branch is SoT)", () => {
			const qs = serializeSearchUrlState({
				q: "bridgestone",
				scope: SEARCH_SCOPE.SINGLE,
				branch: "NL01",
				limit: DEFAULT_SEARCH_LIMIT,
			});

			expect(qs).toBe("q=bridgestone");
		});

		it("serializes MULTI as q + scope=multi + branches", () => {
			const qs = serializeSearchUrlState({
				q: "reifen",
				scope: SEARCH_SCOPE.MULTI,
				branches: ["NL20", "NL06"],
				limit: DEFAULT_SEARCH_LIMIT,
			});

			expect(qs).toBe("q=reifen&scope=multi&branches=NL06%2CNL20");
		});

		it("serializes ALL as q + scope=all", () => {
			const qs = serializeSearchUrlState({
				q: "x",
				scope: SEARCH_SCOPE.ALL,
				limit: DEFAULT_SEARCH_LIMIT,
			});

			expect(qs).toBe("q=x&scope=all");
		});

		it("includes limit when non-default", () => {
			const qs = serializeSearchUrlState({
				q: "x",
				scope: SEARCH_SCOPE.SINGLE,
				branch: "NL01",
				limit: 200,
			});

			expect(qs).toBe("q=x&limit=200");
		});

		it("includes from/to when present (future-proof for RHL-025)", () => {
			const qs = serializeSearchUrlState({
				q: "x",
				scope: SEARCH_SCOPE.SINGLE,
				branch: "NL01",
				limit: 200,
				from: "2025-12-01",
				to: "2025-12-31",
			});

			expect(qs).toBe("q=x&limit=200&from=2025-12-01&to=2025-12-31");
		});
	});
});
