import { useEffect, useRef, useState, useCallback } from "react";
import { createEventSource, probeEventStreamStatus } from "../api/events";
import type { InstanceEventPayload } from "../types/events";

export interface UseEventsReturn {
	events: InstanceEventPayload[];
	connected: boolean;
	reconnectCount: number;
	error: Error | null;
}

const MAX_DELAY = 30000;
const BASE_DELAY = 1000;

const LIFECYCLE_EVENT_TYPES = [
	"instance.created",
	"instance.started",
	"instance.stopped",
	"instance.restarted",
	"instance.deleted",
	"instance.error",
	"instance.health_changed",
] as const;

export function useEvents(): UseEventsReturn {
	const [events, setEvents] = useState<InstanceEventPayload[]>([]);
	const [connected, setConnected] = useState(false);
	const [reconnectCount, setReconnectCount] = useState(0);
	const [error, setError] = useState<Error | null>(null);
	const reconnectAttemptsRef = useRef(0);
	const esRef = useRef<EventSource | null>(null);
	const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
	const isMountedRef = useRef(true);

	const connect = useCallback(() => {
		if (timeoutRef.current) {
			clearTimeout(timeoutRef.current);
			timeoutRef.current = null;
		}

		const es = createEventSource();
		esRef.current = es;

		es.onopen = () => {
			if (!isMountedRef.current) return;
			setConnected(true);
			setError(null);
			reconnectAttemptsRef.current = 0;
			setReconnectCount(0);
		};

		const handleEventMessage = (e: MessageEvent) => {
			if (!isMountedRef.current) return;
			try {
				const payload: InstanceEventPayload = JSON.parse(e.data);
				setEvents((prev) => [...prev, payload]);
			} catch {
				// ignore malformed events
			}
		};

		es.addEventListener("message", handleEventMessage);
		for (const eventType of LIFECYCLE_EVENT_TYPES) {
			es.addEventListener(eventType, handleEventMessage);
		}

		es.onerror = () => {
			if (!isMountedRef.current) return;
			setConnected(false);
			es.close();
			esRef.current = null;

			const attempts = reconnectAttemptsRef.current;
			const delay =
				Math.min(MAX_DELAY, BASE_DELAY * Math.pow(2, attempts)) *
				(0.8 + Math.random() * 0.4);
			reconnectAttemptsRef.current = attempts + 1;
			setReconnectCount(reconnectAttemptsRef.current);

			timeoutRef.current = setTimeout(async () => {
				if (!isMountedRef.current) return;

				const status = await probeEventStreamStatus();
				if (status === 401) {
					const baseUrl =
						import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
					window.location.assign(`${baseUrl}/auth/login`);
					return;
				}

				if (status === 429) {
					const penaltyDelay = delay + 5000;
					timeoutRef.current = setTimeout(() => {
						if (isMountedRef.current) {
							connect();
						}
					}, penaltyDelay);
					return;
				}

				connect();
			}, delay);
		};
	}, []);

	useEffect(() => {
		isMountedRef.current = true;
		connect();
		return () => {
			isMountedRef.current = false;
			if (timeoutRef.current) {
				clearTimeout(timeoutRef.current);
				timeoutRef.current = null;
			}
			if (esRef.current) {
				esRef.current.close();
				esRef.current = null;
			}
		};
	}, [connect]);

	return { events, connected, reconnectCount, error };
}
