import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { useToast } from "../components/Toast";
import { useI18n } from "../i18n";
import { useWallet } from "../wallet/WalletProvider";
import {
  addFavorite,
  getFavoriteIds,
  isFavoritesAuthError,
  removeFavorite,
} from "./api";

type FavoriteStatus = "unknown" | "favorited" | "notFavorited";

type FavoritesContextValue = {
  favoriteIds: Set<string>;
  pendingIds: Set<string>;
  statusFor: (resourceId: string) => FavoriteStatus;
  ensureFavoriteIds: (resourceIds: string[]) => Promise<void>;
  toggleFavorite: (resourceId: string) => Promise<boolean | null>;
  markFavorite: (resourceId: string, favorited: boolean) => void;
};

const FavoritesContext = createContext<FavoritesContextValue | null>(null);

export function FavoritesProvider({ children }: { children: ReactNode }) {
  const { t } = useI18n();
  const { showToast } = useToast();
  const wallet = useWallet();
  const { address, logout, openLoginModal, status, token } = wallet;

  const handleAuthError = useCallback(() => {
    logout();
    openLoginModal();
    showToast(t("favoriteSessionExpired"));
  }, [logout, openLoginModal, showToast, t]);
  const [favoriteIds, setFavoriteIds] = useState<Set<string>>(() => new Set());
  const [knownIds, setKnownIds] = useState<Set<string>>(() => new Set());
  const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set());
  const pendingAfterLoginRef = useRef<string | null>(null);
  const lastAddressRef = useRef<string | null>(null);
  const knownIdsRef = useRef<Set<string>>(new Set());
  const inFlightIdsRef = useRef<Set<string>>(new Set());
  const queuedIdsRef = useRef<Set<string>>(new Set());
  const batchTimerRef = useRef<number | null>(null);
  const tokenRef = useRef<string | null>(null);

  useEffect(() => {
    tokenRef.current = status === "loggedIn" ? token : null;
  }, [status, token]);

  const clearFavoriteStatus = useCallback(() => {
    knownIdsRef.current = new Set();
    inFlightIdsRef.current = new Set();
    queuedIdsRef.current = new Set();
    if (batchTimerRef.current !== null) {
      window.clearTimeout(batchTimerRef.current);
      batchTimerRef.current = null;
    }
    setFavoriteIds(new Set());
    setKnownIds(new Set());
    setPendingIds(new Set());
  }, []);

  useEffect(() => {
    const nextAddress = status === "loggedIn" ? address : null;
    if (lastAddressRef.current === nextAddress) return;
    lastAddressRef.current = nextAddress;
    clearFavoriteStatus();
    if (!nextAddress) pendingAfterLoginRef.current = null;
  }, [address, clearFavoriteStatus, status]);

  useEffect(
    () => () => {
      if (batchTimerRef.current !== null) {
        window.clearTimeout(batchTimerRef.current);
      }
    },
    [],
  );

  const markFavorite = useCallback((resourceId: string, favorited: boolean) => {
    setKnownIds((prev) => {
      const next = new Set(prev).add(resourceId);
      knownIdsRef.current = next;
      return next;
    });
    setFavoriteIds((prev) => {
      const next = new Set(prev);
      if (favorited) next.add(resourceId);
      else next.delete(resourceId);
      return next;
    });
  }, []);

  const flushFavoriteIdBatch = useCallback(async () => {
    const requestToken = tokenRef.current;
    const requestIds = Array.from(queuedIdsRef.current);
    queuedIdsRef.current.clear();
    if (!requestToken || requestIds.length === 0) {
      requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
      return;
    }

    try {
      const ids = await getFavoriteIds(requestToken, requestIds);
      if (tokenRef.current !== requestToken) return;
      setKnownIds((prev) => {
        const next = new Set(prev);
        requestIds.forEach((id) => next.add(id));
        knownIdsRef.current = next;
        return next;
      });
      setFavoriteIds((prev) => {
        const next = new Set(prev);
        ids.forEach((id) => next.add(id));
        return next;
      });
    } catch (error) {
      if (isFavoritesAuthError(error)) handleAuthError();
    } finally {
      requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
      if (queuedIdsRef.current.size > 0 && batchTimerRef.current === null) {
        batchTimerRef.current = window.setTimeout(() => {
          batchTimerRef.current = null;
          void flushFavoriteIdBatch();
        }, 0);
      }
    }
  }, [handleAuthError]);

  const ensureFavoriteIds = useCallback(
    async (resourceIds: string[]) => {
      if (!token || status !== "loggedIn") return;
      const missing = [...new Set(resourceIds)].filter(
        (id) => !knownIdsRef.current.has(id) && !inFlightIdsRef.current.has(id),
      );
      if (missing.length === 0) return;
      missing.forEach((id) => {
        queuedIdsRef.current.add(id);
        inFlightIdsRef.current.add(id);
      });
      if (batchTimerRef.current !== null) return;
      batchTimerRef.current = window.setTimeout(() => {
        batchTimerRef.current = null;
        void flushFavoriteIdBatch();
      }, 0);
    },
    [flushFavoriteIdBatch, status, token],
  );

  const runFavoriteMutation = useCallback(
    async (resourceId: string): Promise<boolean | null> => {
      if (!token) return null;
      const currentlyFavorite = favoriteIds.has(resourceId);
      const nextFavorited = !currentlyFavorite;
      setPendingIds((prev) => new Set(prev).add(resourceId));
      markFavorite(resourceId, nextFavorited);
      try {
        if (currentlyFavorite) await removeFavorite(token, resourceId);
        else await addFavorite(token, resourceId);
        showToast(
          currentlyFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
        );
        return nextFavorited;
      } catch (error) {
        markFavorite(resourceId, currentlyFavorite);
        if (isFavoritesAuthError(error)) handleAuthError();
        else showToast(t("favoriteFailed"), "error");
        throw error;
      } finally {
        setPendingIds((prev) => {
          const next = new Set(prev);
          next.delete(resourceId);
          return next;
        });
      }
    },
    [favoriteIds, handleAuthError, markFavorite, showToast, t, token],
  );

  const toggleFavorite = useCallback(
    async (resourceId: string) => {
      if (!token || status !== "loggedIn") {
        pendingAfterLoginRef.current = resourceId;
        openLoginModal();
        showToast(t("favoriteLoginRequired"));
        return null;
      }
      return runFavoriteMutation(resourceId);
    },
    [openLoginModal, runFavoriteMutation, showToast, status, t, token],
  );

  useEffect(() => {
    if (status !== "loggedIn" || !token) return;
    const pending = pendingAfterLoginRef.current;
    if (!pending) return;
    pendingAfterLoginRef.current = null;
    void runFavoriteMutation(pending).catch(() => undefined);
  }, [runFavoriteMutation, status, token]);

  const statusFor = useCallback(
    (resourceId: string): FavoriteStatus => {
      if (favoriteIds.has(resourceId)) return "favorited";
      if (knownIds.has(resourceId)) return "notFavorited";
      return "unknown";
    },
    [favoriteIds, knownIds],
  );

  const value = useMemo<FavoritesContextValue>(
    () => ({
      favoriteIds,
      pendingIds,
      statusFor,
      ensureFavoriteIds,
      toggleFavorite,
      markFavorite,
    }),
    [
      ensureFavoriteIds,
      favoriteIds,
      markFavorite,
      pendingIds,
      statusFor,
      toggleFavorite,
    ],
  );

  return (
    <FavoritesContext.Provider value={value}>
      {children}
    </FavoritesContext.Provider>
  );
}

export function useFavorites() {
  const ctx = useContext(FavoritesContext);
  if (!ctx)
    throw new Error("useFavorites must be used within FavoritesProvider");
  return ctx;
}
