import { useEffect, useMemo, useRef, useState } from "react";
import type {
    WorkspaceChartInterval,
    WorkspaceChartPoint,
    WorkspaceChartTimeRange,
} from "../../../components/charts/WorkspaceChart";

const BACKEND_URL = "http://localhost:18450";

type HistorianPoint = {
    timestamp: string;
    numericValue: number | null;
    booleanValue: boolean | null;
    textValue: string | null;
};

export function useTelemetryChartSeries(
    sensorKeys: string[],
    timeRange: WorkspaceChartTimeRange,
    interval: WorkspaceChartInterval,
) {
    const [seriesByKey, setSeriesByKey] = useState<Record<string, WorkspaceChartPoint[]>>({});
    const [loading, setLoading] = useState(false);
    const initializedRef = useRef(false);

    const keySignature = useMemo(
        () => sensorKeys.slice().sort().join(","),
        [sensorKeys],
    );

    useEffect(() => {
        if (sensorKeys.length === 0) {
            setSeriesByKey({});
            setLoading(false);
            initializedRef.current = false;
            return;
        }

        const controller = new AbortController();

        async function loadHistory(showLoading: boolean) {
            const startedAt = performance.now();

            try {
                const to = new Date();
                const from = new Date(to.getTime() - rangeToMs(timeRange));

                if (showLoading && !initializedRef.current) {
                    setLoading(true);
                }

                console.log("[TelemetryChartSeries REQUEST]", {
                    sensorKeys,
                    timeRange,
                    interval,
                    from: from.toISOString(),
                    to: to.toISOString(),
                });

                const entries = await Promise.all(
                    sensorKeys.map(async (key) => {
                        const params = new URLSearchParams({
                            key,
                            from: from.toISOString(),
                            to: to.toISOString(),
                        });

                        const url = `${BACKEND_URL}/api/historian/series?${params.toString()}`;

                        const response = await fetch(url, {
                            signal: controller.signal,
                            cache: "no-store",
                        });

                        if (!response.ok) {
                            throw new Error(`Failed to load history for ${key}: ${response.status}`);
                        }

                        const payload = (await response.json()) as HistorianPoint[];

                        const points = payload
                            .filter(
                                (point) =>
                                    point.numericValue !== null &&
                                    Number.isFinite(point.numericValue),
                            )
                            .map((point) => ({
                                timestamp: point.timestamp,
                                value: point.numericValue as number,
                            }));

                        return [key, aggregatePoints(points, interval)] as const;
                    }),
                );

                setSeriesByKey(Object.fromEntries(entries));
                initializedRef.current = true;

                console.log("[TelemetryChartSeries DONE]", {
                    sensorCount: sensorKeys.length,
                    durationMs: Math.round(performance.now() - startedAt),
                });
            } catch (error) {
                if (controller.signal.aborted) return;

                console.error("[TelemetryChartSeries ERROR]", error);

                if (!initializedRef.current) {
                    setSeriesByKey({});
                }
            } finally {
                if (!controller.signal.aborted) {
                    setLoading(false);
                }
            }
        }

        loadHistory(true);

        const refreshMs = getRefreshMs(timeRange);

        if (refreshMs === null) {
            return () => controller.abort();
        }

        const intervalId = window.setInterval(() => {
            loadHistory(false);
        }, refreshMs);

        return () => {
            controller.abort();
            window.clearInterval(intervalId);
        };
    }, [keySignature, timeRange, interval]);

    return {
        seriesByKey,
        loading,
    };
}

function getRefreshMs(range: WorkspaceChartTimeRange): number | null {
    switch (range) {
        case "15m":
        case "1h":
            return 10000;

        case "6h":
            return 30000;

        case "24h":
            return 60000;

        case "7d":
        case "30d":
            return null;
    }
}

function rangeToMs(range: WorkspaceChartTimeRange) {
    switch (range) {
        case "15m":
            return 15 * 60 * 1000;
        case "1h":
            return 60 * 60 * 1000;
        case "6h":
            return 6 * 60 * 60 * 1000;
        case "24h":
            return 24 * 60 * 60 * 1000;
        case "7d":
            return 7 * 24 * 60 * 60 * 1000;
        case "30d":
            return 30 * 24 * 60 * 60 * 1000;
    }
}

function intervalToMs(interval: WorkspaceChartInterval) {
    switch (interval) {
        case "1m":
            return 60 * 1000;
        case "5m":
            return 5 * 60 * 1000;
        case "15m":
            return 15 * 60 * 1000;
        case "1h":
            return 60 * 60 * 1000;
    }
}

function aggregatePoints(
    points: WorkspaceChartPoint[],
    interval: WorkspaceChartInterval,
): WorkspaceChartPoint[] {
    const bucketMs = intervalToMs(interval);
    const buckets = new Map<number, number[]>();

    for (const point of points) {
        const time = new Date(point.timestamp).getTime();

        if (!Number.isFinite(time)) continue;

        const bucketTime = Math.floor(time / bucketMs) * bucketMs;
        const values = buckets.get(bucketTime) ?? [];

        const value = point.value;

        if (value !== null && Number.isFinite(value)) {
            values.push(value);
        }

        buckets.set(bucketTime, values);
    }

    return Array.from(buckets.entries())
        .sort(([a], [b]) => a - b)
        .map(([bucketTime, values]) => ({
            timestamp: new Date(bucketTime).toISOString(),
            value: average(values),
        }))
        .filter((point) => Number.isFinite(point.value));
}

function average(values: number[]) {
    if (values.length === 0) return 0;

    return values.reduce((sum, value) => sum + value, 0) / values.length;
}