import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { distanceInKm } from "@/lib/utils";
import type { SignalDensityCell, Signal, Snapshot, FeedStatus, Point, SnapshotEventPayload } from "@/types/api";

const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8000/api/signals";
const MERCURE_HUB = import.meta.env.VITE_MERCURE_HUB ?? "http://localhost:3000/.well-known/mercure";
const MERCURE_TOPIC = import.meta.env.VITE_MERCURE_TOPIC ?? "https://points-of-interest.local/signals";
const SNAPSHOT_LIMIT = 750;
const VISIBLE_RADIUS_KM = 1;

type ProblemDetails = {
  detail?: unknown;
};

function extractProblemDetail(payload: unknown): string | null {
  if (payload && typeof payload === "object") {
    const { detail } = payload as ProblemDetails;
    if (typeof detail === "string" && detail.trim().length > 0) {
      return detail;
    }
  }
  return null;
}

interface UseHotspotFeedOptions {
  userLocation: Point | null;
  snapshotLimit?: number;
  mercureHub?: string;
  mercureTopic?: string;
}

interface SubmitResult {
  success: boolean;
}

export interface FeedError {
  key: string;
  values?: Record<string, unknown>;
  detail?: string;
}

export function useHotspotFeed({
  userLocation,
  snapshotLimit = SNAPSHOT_LIMIT,
  mercureHub = MERCURE_HUB,
  mercureTopic = MERCURE_TOPIC,
}: UseHotspotFeedOptions) {
  const [status, setStatus] = useState<FeedStatus>("loading");
  const [error, setError] = useState<FeedError | null>(null);
  const [rawPoints, setRawPoints] = useState<Signal[]>([]);
  const [rawDensity, setRawDensity] = useState<SignalDensityCell[]>([]);
  const [rawLatestByUser, setRawLatestByUser] = useState<Signal[]>([]);
  const [clientKey, setClientKey] = useState<string | null>(null);
  const [lastUpdated, setLastUpdated] = useState<string | null>(null);

  const statusRef = useRef<FeedStatus>("loading");
  const initialLoadRef = useRef(true);
  const eventSourceRef = useRef<EventSource | null>(null);

  const setStatusSafe = useCallback((next: FeedStatus) => {
    statusRef.current = next;
    setStatus(next);
  }, []);

  const applySnapshot = useCallback(
    (snapshot: Snapshot, options?: { preserveClientKey?: boolean }) => {
      setRawPoints(snapshot.points);
      setRawDensity(snapshot.density);
      setRawLatestByUser(snapshot.latestByUser);
      if (!options?.preserveClientKey) {
        setClientKey(snapshot.clientKey ?? null);
      }
      setLastUpdated(snapshot.updatedAt ?? new Date().toISOString());
      setError(null);
      initialLoadRef.current = false;
      if (statusRef.current !== "posting") {
        setStatusSafe("idle");
      }
    },
    [setStatusSafe]
  );

  const fetchSnapshot = useCallback(
    async (options?: { silent?: boolean }) => {
      const silent = options?.silent ?? false;
      const previousStatus = statusRef.current;
      const isInitial = initialLoadRef.current;

      if (previousStatus !== "posting") {
        if (isInitial) {
          setStatusSafe("loading");
        } else if (!silent) {
          setStatusSafe("refreshing");
        }
      }

      try {
        const response = await fetch(`${API_BASE}?limit=${snapshotLimit}`, { cache: "no-store" });
        if (!response.ok) {
          const payload = await response.json().catch(() => null);
          const detail = extractProblemDetail(payload);
          setError({ key: "errors.feedUnavailable", detail: detail ?? undefined });
          if (initialLoadRef.current) {
            setStatusSafe("error");
          } else if (previousStatus !== "posting") {
            setStatusSafe("idle");
          }
          return;
        }

        const data: Snapshot = await response.json();
        applySnapshot(data);
      } catch (error) {
        const detail = error instanceof Error && error.message ? error.message : null;
        setError({ key: "errors.feedUnknown", detail: detail ?? undefined });
        if (initialLoadRef.current) {
          setStatusSafe("error");
        } else if (previousStatus !== "posting") {
          setStatusSafe("idle");
        }
      }
    },
    [snapshotLimit, setStatusSafe, applySnapshot]
  );

  const connectToStream = useCallback(() => {
    try {
      eventSourceRef.current?.close();
      eventSourceRef.current = null;

      const url = new URL(mercureHub);
      url.searchParams.append("topic", mercureTopic);

      const eventSource = new EventSource(url.toString());
      eventSource.onmessage = event => {
        try {
          const payload: SnapshotEventPayload = JSON.parse(event.data);
          if (payload?.type === "snapshot" && payload.payload) {
            applySnapshot(payload.payload, { preserveClientKey: true });
          }
        } catch (parseError) {
          console.error("Failed to parse stream payload", parseError);
        }
      };

      eventSource.onerror = () => {
        if (statusRef.current !== "loading") {
          setError({ key: "errors.feedUnknown" });
          setStatusSafe("error");
        }
      };

      eventSourceRef.current = eventSource;
    } catch (connectionError) {
      console.error("Unable to subscribe to live updates", connectionError);
      setError({ key: "errors.feedUnavailable" });
      setStatusSafe("error");
    }
  }, [applySnapshot, mercureHub, mercureTopic, setStatusSafe]);

  useEffect(() => {
    fetchSnapshot().catch(() => undefined);
    connectToStream();

    return () => {
      eventSourceRef.current?.close();
      eventSourceRef.current = null;
    };
  }, [connectToStream, fetchSnapshot]);

  const submitPoint = useCallback(
    async (target: Point): Promise<SubmitResult> => {
      if (!userLocation) {
        setError({
          key: "errors.submitWithReason",
          values: { message: "Location required before submitting." },
          detail: "Location required before submitting.",
        });
        setStatusSafe("error");
        return { success: false };
      }

      setStatusSafe("posting");
      try {
        const response = await fetch(API_BASE, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            signalLocation: target,
            userLocation,
          }),
        });

        if (!response.ok) {
          const payload = await response.json().catch(() => null);
          const detail = extractProblemDetail(payload);
          if (detail) {
            setError({
              key: "errors.submitWithReason",
              values: { message: detail },
              detail,
            });
          } else {
            setError({ key: "errors.submitUnavailable" });
          }
          setStatusSafe("error");
          return { success: false };
        }

        setError(null);
        setStatusSafe("idle");
        return { success: true };
      } catch (error) {
        const detail = error instanceof Error && error.message ? error.message : null;
        if (detail) {
          setError({ key: "errors.submitWithReason", values: { message: detail }, detail });
        } else {
          setError({ key: "errors.submitUnknown" });
        }
        setStatusSafe("error");
        return { success: false };
      }
    },
    [setStatusSafe, userLocation]
  );

  const hasClientKey = Boolean(clientKey);

  const filterDensityWithinRadius = useCallback((collection: SignalDensityCell[], origin: Point | null) => {
    if (!origin) {
      return collection;
    }
    return collection.filter(item => distanceInKm(origin, item) <= VISIBLE_RADIUS_KM);
  }, []);

  const filterPointsWithinRadius = useCallback((collection: Signal[], origin: Point | null) => {
    if (!origin) {
      return collection;
    }
    return collection.filter(item => distanceInKm(origin, item.signalLocation) <= VISIBLE_RADIUS_KM);
  }, []);

  const selectVisibleDensity = useCallback(
    (origin: Point | null) => filterDensityWithinRadius(rawDensity, origin),
    [filterDensityWithinRadius, rawDensity]
  );

  const selectVisiblePoints = useCallback(
    (origin: Point | null) => filterPointsWithinRadius(rawPoints, origin),
    [filterPointsWithinRadius, rawPoints]
  );

  const selectVisibleLatestByUser = useCallback(
    (origin: Point | null) => filterPointsWithinRadius(rawLatestByUser, origin),
    [filterPointsWithinRadius, rawLatestByUser]
  );

  const myLatestPoint = useMemo(() => {
    if (!hasClientKey) {
      return null;
    }
    return rawLatestByUser.find(point => point.userKey === clientKey) ?? null;
  }, [clientKey, hasClientKey, rawLatestByUser]);

  return {
    status,
    error,
    submitPoint,
    fetchSnapshot,
    rawDensity,
    rawPoints,
    rawLatestByUser,
    clientKey,
    lastUpdated,
    hasClientKey,
    selectVisibleDensity,
    selectVisiblePoints,
    selectVisibleLatestByUser,
    myLatestPoint,
  };
}
