CREATE TYPE public.vmid_reservation_status AS ENUM ('reserved', 'released');

CREATE TABLE public.cluster_vmid_allocators (
	cluster_id uuid PRIMARY KEY REFERENCES public.clusters(id) ON DELETE CASCADE,
	next_vmid integer NOT NULL DEFAULT 100,
	created_at timestamptz NOT NULL DEFAULT now(),
	updated_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT cluster_vmid_allocators_next_vmid_check CHECK (next_vmid > 0)
);

CREATE TABLE public.vmid_reservations (
	id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
	cluster_id uuid NOT NULL REFERENCES public.clusters(id) ON DELETE CASCADE,
	proxmox_vmid integer NOT NULL,
	status public.vmid_reservation_status NOT NULL DEFAULT 'reserved',
	created_at timestamptz NOT NULL DEFAULT now(),
	updated_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT vmid_reservations_proxmox_vmid_check CHECK (proxmox_vmid > 0)
);

CREATE UNIQUE INDEX vmid_reservations_reserved_key
	ON public.vmid_reservations (cluster_id, proxmox_vmid)
	WHERE status = 'reserved';

CREATE INDEX vmid_reservations_cluster_id_idx ON public.vmid_reservations (cluster_id);

CREATE OR REPLACE FUNCTION public.reserve_next_vmid(p_cluster_id uuid)
RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE
	candidate integer;
BEGIN
	INSERT INTO public.cluster_vmid_allocators (cluster_id)
	VALUES (p_cluster_id)
	ON CONFLICT (cluster_id) DO NOTHING;

	SELECT next_vmid
	INTO candidate
	FROM public.cluster_vmid_allocators
	WHERE cluster_id = p_cluster_id
	FOR UPDATE;

	LOOP
		EXIT WHEN NOT EXISTS (
			SELECT 1
			FROM public.vms
			WHERE cluster_id = p_cluster_id
				AND proxmox_vmid = candidate
		) AND NOT EXISTS (
			SELECT 1
			FROM public.vmid_reservations
			WHERE cluster_id = p_cluster_id
				AND proxmox_vmid = candidate
				AND status = 'reserved'
		);

		candidate := candidate + 1;
	END LOOP;

	INSERT INTO public.vmid_reservations (cluster_id, proxmox_vmid)
	VALUES (p_cluster_id, candidate);

	UPDATE public.cluster_vmid_allocators
	SET next_vmid = candidate + 1,
		updated_at = now()
	WHERE cluster_id = p_cluster_id;

	RETURN candidate;
END;
$$;
