import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
import { ApiError, login as apiLogin, logout as apiLogout } from "@/lib/api";
import type { LoginResponse } from "@/lib/types";

const STORAGE_KEY = "nxdns_auth_required";

export function readStoredAuthRequired(): boolean | null {
	try {
		const raw = sessionStorage.getItem(STORAGE_KEY);
		return raw === null ? null : raw === "true";
	} catch {
		return null;
	}
}

export function rememberAuthRequired(value: boolean): void {
	try {
		sessionStorage.setItem(STORAGE_KEY, String(value));
	} catch {
		// Storage unavailable; the probe will run again next load.
	}
}

// Deduped across StrictMode double-effects: one empty-password login answers
// whether auth is on (401 → on; 200 with auth_required=false → off).
let probePromise: Promise<boolean> | null = null;

function probeAuthRequired(): Promise<boolean> {
	probePromise ??= apiLogin({ password: "" }).then(
		(response) => {
			rememberAuthRequired(response.auth_required);
			return response.auth_required;
		},
		(error: unknown) => {
			probePromise = null;
			if (error instanceof ApiError && error.status === 401) {
				rememberAuthRequired(true);
				return true;
			}
			throw error;
		},
	);
	return probePromise;
}

export function resetAuthProbeForTests(): void {
	probePromise = null;
}

export interface AuthStore {
	/** null until a login response, a probe, or a stored value settles it. */
	authRequired: boolean | null;
	/** Resolves true when a password is required (form must be shown). */
	probe: () => Promise<boolean>;
	login: (password: string) => Promise<LoginResponse>;
	/** Ends the session server-side; swallows an already-dead session's 401. */
	logout: () => Promise<void>;
}

const AuthContext = createContext<AuthStore | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
	const [authRequired, setAuthRequired] = useState<boolean | null>(readStoredAuthRequired);

	const probe = useCallback(async () => {
		const required = await probeAuthRequired();
		setAuthRequired(required);
		return required;
	}, []);

	useEffect(() => {
		if (authRequired !== null) return;
		probe().catch(() => {
			// Server unreachable; LoginPage's own probe surfaces the error.
		});
	}, [authRequired, probe]);

	const login = useCallback(async (password: string) => {
		const response = await apiLogin({ password });
		rememberAuthRequired(response.auth_required);
		setAuthRequired(response.auth_required);
		return response;
	}, []);

	const logout = useCallback(async () => {
		try {
			await apiLogout();
		} catch (error) {
			if (error instanceof ApiError && error.status === 401) return;
			throw error;
		}
	}, []);

	const value = useMemo<AuthStore>(
		() => ({ authRequired, probe, login, logout }),
		[authRequired, probe, login, logout],
	);
	return <AuthContext value={value}>{children}</AuthContext>;
}

export function useAuth(): AuthStore {
	const store = useContext(AuthContext);
	if (store === null) throw new Error("useAuth requires an AuthProvider");
	return store;
}
