/**
 * useOfflineQuery Hook
 *
 * Hook for offline-first data fetching with caching
 */

import { useState, useEffect, useCallback, useRef } from 'react';
import { cacheService, useOfflineStore } from '@/services/offline';

interface UseOfflineQueryOptions<T> {
  cacheKey: string;
  fetcher: () => Promise<T>;
  ttl?: number;
  enabled?: boolean;
  refetchOnOnline?: boolean;
  staleWhileRevalidate?: boolean;
}

interface UseOfflineQueryResult<T> {
  data: T | null;
  isLoading: boolean;
  isStale: boolean;
  error: Error | null;
  refetch: () => Promise<void>;
  isFetching: boolean;
}

export function useOfflineQuery<T>({
  cacheKey,
  fetcher,
  ttl = 5 * 60 * 1000,
  enabled = true,
  refetchOnOnline = true,
  staleWhileRevalidate = true,
}: UseOfflineQueryOptions<T>): UseOfflineQueryResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isFetching, setIsFetching] = useState(false);
  const [isStale, setIsStale] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const { isOnline } = useOfflineStore();
  const prevOnlineRef = useRef(isOnline);

  const fetchData = useCallback(async (force: boolean = false) => {
    if (!enabled) return;

    setIsFetching(true);

    try {
      // Try cache first
      if (!force) {
        const cached = staleWhileRevalidate
          ? await cacheService.getStale<T>(cacheKey)
          : null;

        if (cached) {
          setData(cached.data);
          setIsStale(cached.isStale);
          setIsLoading(false);

          // If not stale, don't fetch
          if (!cached.isStale) {
            setIsFetching(false);
            return;
          }
        } else {
          const freshCache = await cacheService.get<T>(cacheKey);
          if (freshCache) {
            setData(freshCache);
            setIsStale(false);
            setIsLoading(false);
            setIsFetching(false);
            return;
          }
        }
      }

      // Fetch from network if online
      if (isOnline) {
        const result = await fetcher();
        await cacheService.set(cacheKey, result, ttl);
        setData(result);
        setIsStale(false);
        setError(null);
      } else if (!data) {
        // Offline and no cached data
        const staleData = await cacheService.getStale<T>(cacheKey);
        if (staleData) {
          setData(staleData.data);
          setIsStale(true);
        } else {
          setError(new Error('No internet connection and no cached data'));
        }
      }
    } catch (err) {
      const fetchError = err instanceof Error ? err : new Error('Unknown error');
      setError(fetchError);

      // Try to use stale cache on error
      const staleData = await cacheService.getStale<T>(cacheKey);
      if (staleData) {
        setData(staleData.data);
        setIsStale(true);
      }
    } finally {
      setIsLoading(false);
      setIsFetching(false);
    }
  }, [cacheKey, fetcher, ttl, enabled, staleWhileRevalidate, isOnline, data]);

  // Initial fetch
  useEffect(() => {
    fetchData();
  }, [cacheKey, enabled]);

  // Refetch when coming online
  useEffect(() => {
    if (refetchOnOnline && !prevOnlineRef.current && isOnline) {
      fetchData(true);
    }
    prevOnlineRef.current = isOnline;
  }, [isOnline, refetchOnOnline, fetchData]);

  const refetch = useCallback(async () => {
    await fetchData(true);
  }, [fetchData]);

  return {
    data,
    isLoading,
    isStale,
    error,
    refetch,
    isFetching,
  };
}

// Mutation hook for offline-capable mutations
interface UseOfflineMutationOptions<TData, TVariables> {
  entity: 'partner' | 'product' | 'invoice';
  action: 'create' | 'update' | 'delete';
  mutationFn: (variables: TVariables) => Promise<TData>;
  onSuccess?: (data: TData, variables: TVariables) => void;
  onError?: (error: Error, variables: TVariables) => void;
  invalidateKeys?: string[];
}

interface UseOfflineMutationResult<TData, TVariables> {
  mutate: (variables: TVariables) => Promise<TData | null>;
  isLoading: boolean;
  error: Error | null;
  data: TData | null;
}

export function useOfflineMutation<TData, TVariables extends Record<string, any>>({
  entity,
  action,
  mutationFn,
  onSuccess,
  onError,
  invalidateKeys = [],
}: UseOfflineMutationOptions<TData, TVariables>): UseOfflineMutationResult<TData, TVariables> {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [data, setData] = useState<TData | null>(null);

  const { isOnline, addToQueue } = useOfflineStore();

  const mutate = useCallback(async (variables: TVariables): Promise<TData | null> => {
    setIsLoading(true);
    setError(null);

    try {
      if (isOnline) {
        // Online: execute immediately
        const result = await mutationFn(variables);
        setData(result);

        // Invalidate cache keys
        for (const key of invalidateKeys) {
          await cacheService.remove(key);
        }

        onSuccess?.(result, variables);
        return result;
      } else {
        // Offline: queue for later sync
        addToQueue({
          entity,
          action,
          data: variables,
        });

        onSuccess?.(null as any, variables);
        return null;
      }
    } catch (err) {
      const mutationError = err instanceof Error ? err : new Error('Mutation failed');
      setError(mutationError);
      onError?.(mutationError, variables);

      // If network error, queue for later
      if (!isOnline || (err as any)?.code === 'NETWORK_ERROR') {
        addToQueue({
          entity,
          action,
          data: variables,
        });
      }

      throw mutationError;
    } finally {
      setIsLoading(false);
    }
  }, [isOnline, mutationFn, entity, action, addToQueue, invalidateKeys, onSuccess, onError]);

  return {
    mutate,
    isLoading,
    error,
    data,
  };
}
