"use client";

import React from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { RefreshCw } from "lucide-react";

import { useAuth } from "@/components/auth/authContext";
import { ApiClientError, getBranches, search } from "@/lib/frontend/apiClient";
import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
import { searchPath } from "@/lib/frontend/routes";

import {
	SEARCH_SCOPE,
	parseSearchUrlState,
	serializeSearchUrlState,
} from "@/lib/frontend/search/urlState";
import { mapSearchError } from "@/lib/frontend/search/errorMapping";

import ExplorerPageShell from "@/components/explorer/ExplorerPageShell";
import ExplorerSectionCard from "@/components/explorer/ExplorerSectionCard";
import ForbiddenView from "@/components/system/ForbiddenView";
import { Button } from "@/components/ui/button";

import SearchForm from "@/components/search/SearchForm";
import SearchResults from "@/components/search/SearchResults";

const PAGE_LIMIT = 100;

const BRANCH_LIST_STATE = Object.freeze({
	IDLE: "idle",
	LOADING: "loading",
	READY: "ready",
	ERROR: "error",
});

export default function SearchPage({ branch }) {
	const router = useRouter();
	const searchParams = useSearchParams();
	const { status, user } = useAuth();

	const isAuthenticated = status === "authenticated" && user;
	const isAdminDev =
		isAuthenticated && (user.role === "admin" || user.role === "dev");
	const isBranchUser = isAuthenticated && user.role === "branch";

	const parsedUrlState = React.useMemo(() => {
		return parseSearchUrlState(searchParams, { routeBranch: branch });
	}, [searchParams, branch]);

	// Enforce "single = this route branch" and keep branch users safe.
	const urlState = React.useMemo(() => {
		// AuthGate ensures auth before rendering, but keep this defensive.
		if (!user) return parsedUrlState;

		// Branch users: always single, no cross-branch scope params.
		if (user.role === "branch") {
			return {
				...parsedUrlState,
				scope: SEARCH_SCOPE.SINGLE,
				branch,
				branches: [],
			};
		}

		// Admin/dev: single scope is always the route branch context.
		if (parsedUrlState.scope === SEARCH_SCOPE.SINGLE) {
			return { ...parsedUrlState, branch };
		}

		return parsedUrlState;
	}, [parsedUrlState, user, branch]);

	const searchKey = React.useMemo(() => {
		// This is our "query identity" without cursor.
		return serializeSearchUrlState(urlState);
	}, [urlState]);

	// Keep a ref of the latest key so async "load more" cannot append to a new search.
	const searchKeyRef = React.useRef(searchKey);
	React.useEffect(() => {
		searchKeyRef.current = searchKey;
	}, [searchKey]);

	// Input draft (URL remains the single source of truth for executed searches).
	const [qDraft, setQDraft] = React.useState(urlState.q || "");
	React.useEffect(() => {
		setQDraft(urlState.q || "");
	}, [urlState.q]);

	// Admin/dev: load branch list for multi-select (fail-open).
	const [branchList, setBranchList] = React.useState({
		status: BRANCH_LIST_STATE.IDLE,
		branches: null,
	});

	React.useEffect(() => {
		if (!isAdminDev) return;

		let cancelled = false;
		setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null });

		(async () => {
			try {
				const res = await getBranches();
				if (cancelled) return;

				const branches = Array.isArray(res?.branches) ? res.branches : [];
				setBranchList({ status: BRANCH_LIST_STATE.READY, branches });
			} catch (err) {
				if (cancelled) return;

				console.error("[SearchPage] getBranches failed:", err);
				setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
			}
		})();

		return () => {
			cancelled = true;
		};
	}, [isAdminDev, user?.userId]);

	// Search results state.
	const [statusState, setStatusState] = React.useState("idle"); // idle|loading|success|error
	const [items, setItems] = React.useState([]);
	const [nextCursor, setNextCursor] = React.useState(null);
	const [error, setError] = React.useState(null);

	const [isLoadingMore, setIsLoadingMore] = React.useState(false);
	const [loadMoreError, setLoadMoreError] = React.useState(null);

	const searchRequestIdRef = React.useRef(0);
	const loadMoreRequestIdRef = React.useRef(0);

	const mappedError = React.useMemo(() => mapSearchError(error), [error]);
	const mappedLoadMoreError = React.useMemo(
		() => mapSearchError(loadMoreError),
		[loadMoreError]
	);

	// Redirect on unauthenticated search calls (session expired mid-session).
	React.useEffect(() => {
		if (mappedError?.kind !== "unauthenticated") return;

		const next =
			typeof window !== "undefined"
				? `${window.location.pathname}${window.location.search}`
				: searchPath(branch);

		window.location.replace(
			buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next })
		);
	}, [mappedError?.kind, branch]);

	function buildHref(nextState, { push } = {}) {
		const base = searchPath(branch);
		const qs = serializeSearchUrlState(nextState);
		const href = qs ? `${base}?${qs}` : base;

		if (push) router.push(href);
		else router.replace(href);
	}

	function handleSubmit() {
		const nextState = {
			q: qDraft,
			// Branch users always single; admin/dev use selected scope.
			scope: isAdminDev ? urlState.scope : SEARCH_SCOPE.SINGLE,
			branch, // explicit for shareability (even though route already contains it)
			branches: urlState.branches,
			from: urlState.from,
			to: urlState.to,
		};

		buildHref(nextState, { push: true });
	}

	function handleScopeChange(nextScope) {
		if (!isAdminDev) return;

		const nextState = {
			// Scope changes rerun the currently executed query (URL q), not the draft.
			q: urlState.q,
			scope: nextScope,
			branch,
			branches: nextScope === SEARCH_SCOPE.MULTI ? urlState.branches : [],
			from: urlState.from,
			to: urlState.to,
		};

		buildHref(nextState, { push: false });
	}

	function toggleMultiBranch(branchId) {
		if (!isAdminDev) return;

		const current = Array.isArray(urlState.branches) ? urlState.branches : [];
		const set = new Set(current);

		if (set.has(branchId)) set.delete(branchId);
		else set.add(branchId);

		const nextState = {
			q: urlState.q,
			scope: SEARCH_SCOPE.MULTI,
			branches: Array.from(set),
			from: urlState.from,
			to: urlState.to,
		};

		buildHref(nextState, { push: false });
	}

	function buildSearchRequest({ cursor = null } = {}) {
		const q = urlState.q;

		if (!q) return null;

		const base = {
			q,
			limit: PAGE_LIMIT,
		};

		if (urlState.from) base.from = urlState.from;
		if (urlState.to) base.to = urlState.to;
		if (cursor) base.cursor = cursor;

		// Branch role: never send scope/branches from URL (avoid forbidden / keep safe).
		if (isBranchUser) {
			base.branch = branch;
			return base;
		}

		// Admin/dev scopes:
		if (urlState.scope === SEARCH_SCOPE.ALL) {
			base.scope = "all";
			return base;
		}

		if (urlState.scope === SEARCH_SCOPE.MULTI) {
			base.scope = "multi";
			base.branches = urlState.branches;
			return base;
		}

		// Single (explicit branch param for shareability)
		base.branch = branch;
		return base;
	}

	async function runFirstPage() {
		// Reset "load more" UI whenever we start a new first-page search.
		setIsLoadingMore(false);
		setLoadMoreError(null);

		const q = urlState.q;

		// No search yet.
		if (!q) {
			setStatusState("idle");
			setItems([]);
			setNextCursor(null);
			setError(null);
			return;
		}

		// Local validation: multi scope requires at least one branch.
		if (isAdminDev && urlState.scope === SEARCH_SCOPE.MULTI) {
			const branches = Array.isArray(urlState.branches)
				? urlState.branches
				: [];
			if (branches.length === 0) {
				setStatusState("error");
				setItems([]);
				setNextCursor(null);
				setError(
					new ApiClientError({
						status: 400,
						code: "VALIDATION_SEARCH_BRANCHES",
						message: "Invalid branches",
					})
				);
				return;
			}
		}

		const req = buildSearchRequest({ cursor: null });
		if (!req) return;

		const id = ++searchRequestIdRef.current;

		setStatusState("loading");
		setItems([]);
		setNextCursor(null);
		setError(null);

		try {
			const res = await search(req);

			if (id !== searchRequestIdRef.current) return;

			const nextItems = Array.isArray(res?.items) ? res.items : [];
			const next = typeof res?.nextCursor === "string" ? res.nextCursor : null;

			setItems(nextItems);
			setNextCursor(next);
			setStatusState("success");
		} catch (err) {
			if (id !== searchRequestIdRef.current) return;

			setItems([]);
			setNextCursor(null);
			setError(err);
			setStatusState("error");
		}
	}

	async function loadMore() {
		if (!nextCursor) return;
		if (isLoadingMore) return;

		const baseKey = searchKeyRef.current;

		const req = buildSearchRequest({ cursor: nextCursor });
		if (!req) return;

		const id = ++loadMoreRequestIdRef.current;

		setIsLoadingMore(true);
		setLoadMoreError(null);

		try {
			const res = await search(req);

			// If a newer "load more" started, ignore this result.
			if (id !== loadMoreRequestIdRef.current) return;

			// If the base search changed, do not append.
			if (searchKeyRef.current !== baseKey) return;

			const moreItems = Array.isArray(res?.items) ? res.items : [];
			const next = typeof res?.nextCursor === "string" ? res.nextCursor : null;

			setItems((prev) => [...prev, ...moreItems]);
			setNextCursor(next);
		} catch (err) {
			if (id !== loadMoreRequestIdRef.current) return;
			setLoadMoreError(err);
		} finally {
			if (id === loadMoreRequestIdRef.current) {
				setIsLoadingMore(false);
			}
		}
	}

	// Run first-page search whenever the URL-driven search identity changes.
	React.useEffect(() => {
		runFirstPage();
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [searchKey, isAdminDev, isBranchUser, branch]);

	// Forbidden (keep consistent with Explorer UX).
	if (mappedError?.kind === "forbidden") {
		return <ForbiddenView attemptedBranch={branch} />;
	}

	const actions = (
		<Button
			variant="outline"
			size="sm"
			onClick={() => runFirstPage()}
			disabled={!urlState.q || statusState === "loading"}
			title="Aktualisieren"
		>
			<RefreshCw className="h-4 w-4" />
			Aktualisieren
		</Button>
	);

	const resultsHeaderRight = urlState.q ? (
		<span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
			{urlState.q}
		</span>
	) : null;

	const resultsDescription = urlState.q
		? `Niederlassung ${branch}`
		: "Geben Sie einen Suchbegriff ein, um zu starten.";

	return (
		<ExplorerPageShell
			title="Suche"
			description={`Lieferscheine durchsuchen • Niederlassung ${branch}`}
			actions={actions}
		>
			<ExplorerSectionCard
				title="Suche"
				description="Suchbegriff und Suchbereich auswählen."
			>
				<SearchForm
					branch={branch}
					qDraft={qDraft}
					onQDraftChange={setQDraft}
					onSubmit={handleSubmit}
					currentQuery={urlState.q}
					isSubmitting={statusState === "loading"}
					isAdminDev={isAdminDev}
					scope={urlState.scope}
					onScopeChange={handleScopeChange}
					availableBranches={
						branchList.status === BRANCH_LIST_STATE.READY &&
						Array.isArray(branchList.branches)
							? branchList.branches
							: []
					}
					branchesStatus={branchList.status}
					selectedBranches={urlState.branches}
					onToggleBranch={toggleMultiBranch}
				/>
			</ExplorerSectionCard>

			<ExplorerSectionCard
				title="Ergebnisse"
				description={resultsDescription}
				headerRight={resultsHeaderRight}
			>
				<SearchResults
					branch={branch}
					scope={urlState.scope}
					status={statusState}
					items={items}
					error={mappedError}
					onRetry={runFirstPage}
					nextCursor={nextCursor}
					onLoadMore={loadMore}
					isLoadingMore={isLoadingMore}
					loadMoreError={mappedLoadMoreError}
				/>
			</ExplorerSectionCard>
		</ExplorerPageShell>
	);
}
