import { getSession } from "@/lib/auth/session";
import { canAccessBranch } from "@/lib/auth/permissions";
import {
	withErrorHandling,
	json,
	badRequest,
	unauthorized,
	forbidden,
} from "@/lib/api/errors";
import { search as searchBackend } from "@/lib/search";

/**
 * Search API (RHL-016)
 *
 * - Sync-first (Qsirch /search/) with cursor-based pagination.
 * - Provider abstraction allows later switch to async-search without changing this endpoint.
 *
 * Query params:
 * - scope: "branch" | "multi" | "all" (admin/dev only; branch users are forced to their own branch)
 * - branch: NLxx (for scope=branch)
 * - branches: comma-separated NLxx list (for scope=multi)
 * - q: optional text query
 * - from/to: optional inclusive ISO date (YYYY-MM-DD)
 * - limit: optional (default 100, allowed 50..200)
 * - cursor: optional opaque string
 */

export const dynamic = "force-dynamic";

const BRANCH_RE = /^NL\d+$/;
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;

function isValidIsoDate(value) {
	if (!ISO_DATE_RE.test(value)) return false;

	const [y, m, d] = value.split("-").map((x) => Number(x));
	if (!Number.isInteger(y) || !Number.isInteger(m) || !Number.isInteger(d))
		return false;

	// Basic calendar sanity (month/day ranges; does not validate month-length precisely,
	// but rejects obvious invalid formats and keeps behavior predictable).
	if (m < 1 || m > 12) return false;
	if (d < 1 || d > 31) return false;

	return true;
}

function parseLimitOrThrow(raw) {
	if (raw === null || raw === undefined || raw === "") return 100;

	if (!/^\d+$/.test(String(raw))) {
		throw badRequest("VALIDATION_SEARCH_LIMIT", "Invalid limit parameter", {
			limit: raw,
		});
	}

	const n = Number(raw);
	if (!Number.isInteger(n) || n < 50 || n > 200) {
		throw badRequest("VALIDATION_SEARCH_LIMIT", "Invalid limit parameter", {
			limit: n,
			min: 50,
			max: 200,
		});
	}

	return n;
}

function parseScope(raw) {
	const s = (raw || "").trim().toLowerCase();
	if (!s) return null;
	if (s === "branch" || s === "multi" || s === "all") return s;
	throw badRequest("VALIDATION_SEARCH_SCOPE", "Invalid scope parameter", {
		scope: raw,
	});
}

function parseBranchesCsv(raw) {
	if (!raw) return [];

	return String(raw)
		.split(",")
		.map((x) => x.trim())
		.filter(Boolean);
}

function unique(items) {
	return Array.from(new Set(items));
}

export const GET = withErrorHandling(
	async function GET(request) {
		const session = await getSession();

		if (!session) {
			throw unauthorized("AUTH_UNAUTHENTICATED", "Unauthorized");
		}

		const { searchParams } = new URL(request.url);

		const rawScope = searchParams.get("scope");
		const scope = parseScope(rawScope);

		const qRaw = searchParams.get("q");
		const q = typeof qRaw === "string" && qRaw.trim() ? qRaw.trim() : null;

		const fromRaw = searchParams.get("from");
		const toRaw = searchParams.get("to");

		const from =
			typeof fromRaw === "string" && fromRaw.trim() ? fromRaw.trim() : null;
		const to = typeof toRaw === "string" && toRaw.trim() ? toRaw.trim() : null;

		if (from && !isValidIsoDate(from)) {
			throw badRequest("VALIDATION_SEARCH_DATE", "Invalid from date", { from });
		}
		if (to && !isValidIsoDate(to)) {
			throw badRequest("VALIDATION_SEARCH_DATE", "Invalid to date", { to });
		}
		if (from && to && from > to) {
			throw badRequest("VALIDATION_SEARCH_RANGE", "Invalid date range", {
				from,
				to,
			});
		}

		// At least one of q or date range must be provided to avoid "match everything"
		// queries by accident (especially dangerous for global admin searches).
		if (!q && !from && !to) {
			throw badRequest(
				"VALIDATION_SEARCH_MISSING_FILTER",
				"At least one of q or date range must be provided"
			);
		}

		const limit = parseLimitOrThrow(searchParams.get("limit"));
		const cursor = searchParams.get("cursor");

		const branchParam = searchParams.get("branch");
		const branchesParam = searchParams.get("branches");

		const requestedSingleBranch =
			typeof branchParam === "string" && branchParam.trim()
				? branchParam.trim()
				: null;

		const requestedBranches =
			branchesParam && String(branchesParam).trim()
				? unique(parseBranchesCsv(branchesParam))
				: [];

		// RBAC: branch users are forced to their own branch.
		if (session.role === "branch") {
			if (!session.branchId) {
				throw forbidden("AUTH_FORBIDDEN_BRANCH", "Forbidden");
			}

			// If the caller attempts to query other branches, reject.
			if (requestedSingleBranch && requestedSingleBranch !== session.branchId) {
				throw forbidden("AUTH_FORBIDDEN_BRANCH", "Forbidden");
			}
			if (requestedBranches.length > 0) {
				const hasForeign = requestedBranches.some(
					(b) => b !== session.branchId
				);
				if (hasForeign) throw forbidden("AUTH_FORBIDDEN_BRANCH", "Forbidden");
			}
			if (scope === "all") {
				throw forbidden("AUTH_FORBIDDEN_BRANCH", "Forbidden");
			}

			return json(
				await searchBackend({
					mode: "branch",
					branches: [session.branchId],
					q,
					from,
					to,
					limit,
					cursor: cursor ? String(cursor) : null,
				}),
				200
			);
		}

		// Admin/dev: scope can be branch/multi/all
		let mode;
		let branches = null;

		if (scope === "all") {
			mode = "all";
			branches = null;
		} else if (requestedBranches.length > 0 || scope === "multi") {
			mode = "multi";
			branches = requestedBranches;

			if (!Array.isArray(branches) || branches.length === 0) {
				throw badRequest(
					"VALIDATION_SEARCH_BRANCHES",
					"Missing branches parameter for multi scope"
				);
			}
		} else {
			// Default to branch scope
			mode = "branch";
			if (!requestedSingleBranch) {
				throw badRequest(
					"VALIDATION_SEARCH_BRANCH",
					"Missing branch parameter for branch scope"
				);
			}
			branches = [requestedSingleBranch];
		}

		// Validate branch patterns (even for admin/dev) to avoid weird inputs.
		if (mode === "branch" || mode === "multi") {
			for (const b of branches) {
				if (!BRANCH_RE.test(b)) {
					throw badRequest(
						"VALIDATION_SEARCH_BRANCH",
						"Invalid branch parameter",
						{
							branch: b,
						}
					);
				}
				// Enforce RBAC helper semantics consistently, even though admin/dev will pass.
				if (!canAccessBranch(session, b)) {
					throw forbidden("AUTH_FORBIDDEN_BRANCH", "Forbidden");
				}
			}
		}

		const result = await searchBackend({
			mode,
			branches,
			q,
			from,
			to,
			limit,
			cursor: cursor ? String(cursor) : null,
		});

		return json(result, 200);
	},
	{ logPrefix: "[api/search]" }
);
