import { useCallback, useEffect, useState, useRef } from 'react';
import { usePersSDK } from '../providers/PersSDKProvider';
import { useWeb3 } from './useWeb3';
import { NativeTokenTypes, type TokenDTO } from '@explorins/pers-sdk';
import type { TokenBalance, TokenBalanceRequest } from '@explorins/pers-sdk/web3';

/**
 * Token balance with associated token information for display
 * 
 * This composite type combines blockchain balance data with token metadata,
 * making it easy to display token information in UI components without
 * needing to join data from multiple sources.
 */
export interface TokenBalanceWithToken {
  /** The balance data from blockchain */
  tokenBalance: TokenBalance;
  /** The token definition (contract, symbol, metadata, etc.) */
  token: TokenDTO;
  /** Human-readable formatted balance string (e.g., "100 XPL" or "3 stamps") */
  balanceFormatted: string;
}

/**
 * Configuration options for useTokenBalances hook
 */
export interface UseTokenBalancesOptions {
  /** 
   * Wallet address to check balances for (REQUIRED)
   * Apps must explicitly provide the wallet address - no automatic selection
   * Get from: user.wallets[selectedIndex].address
   */
  accountAddress: string;
  /** Available tokens to check balances for (from SDK or filtered list) */
  availableTokens?: TokenDTO[];
  /** Whether to automatically load balances on mount/auth/token changes */
  autoLoad?: boolean;
  /** 
   * Fallback polling interval in milliseconds (0 = disabled)
   * @deprecated Use wallet events instead (refreshOnWalletEvents). Polling kept as fallback only.
   * @default 0
   */
  refreshInterval?: number;
  /** 
   * Auto-refresh balances when wallet events are received (Transfer, Approval, etc.)
   * Requires `captureWalletEvents: true` in SDK config (default)
   * @default true
   */
  refreshOnWalletEvents?: boolean;
  /**
   * Debounce time for wallet event-triggered refreshes (milliseconds)
   * Prevents rapid refresh calls during batch transfers
   * @default 1000
   */
  walletEventDebounceMs?: number;
}

/**
 * Token balance loading hook return value
 */
export interface UseTokenBalancesResult {
  /** Array of token balances with formatted display data */
  tokenBalances: TokenBalanceWithToken[];
  /** Whether balances are currently loading */
  isLoading: boolean;
  /** Error message if loading failed */
  error: string | null;
  /** Manually trigger balance refresh */
  refresh: () => Promise<void>;
  /** Whether the hook is available (SDK initialized, user authenticated, has wallet) */
  isAvailable: boolean;
  /** User's wallet address being queried */
  userAccountAddress: string | null;
}

/**
 * React hook for loading and managing token balances
 * 
 * Automatically handles:
 * - Loading balances for ERC20 (fungible tokens/points)
 * - Loading collections for ERC721 (unique NFTs/stamps)
 * - Loading collections for ERC1155 (semi-fungible tokens/rewards)
 * - Parallel loading with error resilience
 * - Auto-refresh on authentication or token list changes
 * - Formatted balance strings for display
 * 
 * This hook consolidates complex token balance loading logic that would otherwise
 * need to be duplicated across every app. It handles the differences between
 * fungible and non-fungible tokens, parallel loading, error handling, and
 * provides formatted display strings.
 * 
 * @param options - Configuration options
 * @returns Token balances state and actions
 * 
 * @example
 * **Basic Usage:**
 * ```typescript
 * import { useTokenBalances, useTokens, usePersSDK } from '@explorins/pers-sdk-react-native';
 * import { useState, useEffect } from 'react';
 * 
 * function TokenBalancesScreen() {
 *   const { user } = usePersSDK();
 *   const { getTokens } = useTokens();
 *   const [availableTokens, setAvailableTokens] = useState([]);
 *   
 *   // Load tokens on mount
 *   useEffect(() => {
 *     getTokens().then(result => setAvailableTokens(result.data));
 *   }, []);
 *   
 *   // App explicitly selects which wallet to use
 *   const walletAddress = user?.wallets?.[0]?.address;
 *   
 *   const { 
 *     tokenBalances, 
 *     isLoading, 
 *     error, 
 *     refresh 
 *   } = useTokenBalances({
 *     accountAddress: walletAddress!,
 *     availableTokens,
 *     autoLoad: true
 *   });
 *   
 *   if (isLoading) return <Text>Loading balances...</Text>;
 *   if (error) return <Text>Error: {error}</Text>;
 *   
 *   return (
 *     <View>
 *       {tokenBalances.map(({ token, balanceFormatted }) => (
 *         <View key={token.id}>
 *           <Text>{token.name}: {balanceFormatted}</Text>
 *         </View>
 *       ))}
 *       <Button onPress={refresh} title="Refresh" />
 *     </View>
 *   );
 * }
 * ```
 * 
 * @example
 * **With Wallet Events (Real-time):
 * ```typescript
 * // Auto-refresh on Transfer, Approval, and other blockchain events
 * const { tokenBalances } = useTokenBalances({
 *   accountAddress: walletAddress!,
 *   availableTokens,
 *   refreshOnWalletEvents: true,    // Enable real-time refresh (default: true)
 *   walletEventDebounceMs: 1000     // Debounce rapid events (default: 1000ms)
 * });
 * ```
 * 
 * @example
 * **Multi-Wallet Support:**
 * ```typescript
 * function MultiWalletBalances() {
 *   const { user } = usePersSDK();
 *   const [selectedWalletIndex, setSelectedWalletIndex] = useState(0);
 *   
 *   const walletAddress = user?.wallets?.[selectedWalletIndex]?.address;
 *   
 *   const { tokenBalances } = useTokenBalances({
 *     accountAddress: walletAddress!,
 *     availableTokens
 *   });
 *   
 *   // UI lets user switch between wallets
 * }
 * ```
 */
export function useTokenBalances(options: UseTokenBalancesOptions): UseTokenBalancesResult {
  const {
    accountAddress,
    availableTokens = [],
    autoLoad = true,
    refreshInterval = 0,
    refreshOnWalletEvents = true,
    walletEventDebounceMs = 1000
  } = options;

  const { isAuthenticated, sdk } = usePersSDK();
  const web3 = useWeb3();
  
  const [tokenBalances, setTokenBalances] = useState<TokenBalanceWithToken[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  
  // Debounce ref for wallet events
  const walletEventDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Check if the hook is available for use
  const isAvailable = web3.isAvailable && isAuthenticated && !!accountAddress;

  /**
   * Process balance for ERC20 fungible tokens (points/credits)
   */
  const processPointsBalance = useCallback(async (token: TokenDTO): Promise<TokenBalanceWithToken | null> => {
    if (!web3.getTokenBalance || !accountAddress) return null;
    
    try {
      const request: TokenBalanceRequest = {
        accountAddress,
        contractAddress: token.contractAddress,
        abi: token.abi,
        tokenId: '',
        chainId: token.chainId
      };
      
      const balanceResult = await web3.getTokenBalance(request);
      
      if (!balanceResult || !balanceResult.hasBalance) return null;
      
      return {
        tokenBalance: balanceResult,
        token,
        balanceFormatted: `${balanceResult.balance} ${token.symbol || 'tokens'}`
      };
    } catch (err) {
      console.warn(`[useTokenBalances] Failed to load balance for ${token.symbol}:`, err);
      return null;
    }
  }, [web3.getTokenBalance, accountAddress]);

  /**
   * Process collection for ERC721/ERC1155 non-fungible tokens (stamps/rewards)
   */
  const processRewardsBalance = useCallback(async (token: TokenDTO): Promise<TokenBalanceWithToken[]> => {
    if (!web3.getAccountOwnedTokensFromContract || !accountAddress) return [];
    
    try {
      const result = await web3.getAccountOwnedTokensFromContract(accountAddress, token);
      
      if (result.totalOwned === 0) return [];
      
      const balanceUnit = token.type === NativeTokenTypes.ERC1155 ? 'reward' : 'stamp';
      
      // Return one item per tokenId (for individual display in lists)
      return result.ownedTokens.map(ownedToken => ({
        tokenBalance: ownedToken,
        token,
        balanceFormatted: `${ownedToken.balance} ${balanceUnit}${ownedToken.balance !== 1 ? 's' : ''}`
      }));
    } catch (err) {
      console.warn(`[useTokenBalances] Failed to load collection for ${token.symbol}:`, err);
      return [];
    }
  }, [web3.getAccountOwnedTokensFromContract, accountAddress]);

  /**
   * Load all token balances
   */
  const loadBalances = useCallback(async () => {
    if (!isAvailable || availableTokens.length === 0) {
      return;
    }

    setIsLoading(true);
    setError(null);
    
    try {
      // Split by category: points (fungible credits) vs rewards (stamps/collectibles)
      const points = availableTokens.filter(t => t.type === NativeTokenTypes.ERC20);
      const rewards = availableTokens.filter(
        t => t.type === NativeTokenTypes.ERC721 || t.type === NativeTokenTypes.ERC1155
      );
      
      // Load all balances in parallel (individual errors handled per-token)
      const [pointsResults, rewardsResults] = await Promise.all([
        Promise.all(points.map(processPointsBalance)),
        Promise.all(rewards.map(processRewardsBalance))
      ]);
      
      // Combine results, filtering out nulls (failed loads return null/empty)
      const allBalances = [
        ...pointsResults.filter(Boolean),
        ...rewardsResults.flat()
      ] as TokenBalanceWithToken[];
      
      setTokenBalances(allBalances);
      
      // Set error only if no balances loaded at all
      if (allBalances.length === 0 && availableTokens.length > 0) {
        setError('No token balances could be loaded');
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to load balances';
      setError(errorMessage);
      console.error('[useTokenBalances] Error loading balances:', err);
    } finally {
      setIsLoading(false);
    }
  }, [isAvailable, availableTokens, processPointsBalance, processRewardsBalance]);

  // Auto-load balances when dependencies change
  useEffect(() => {
    if (autoLoad && isAvailable && availableTokens.length > 0) {
      loadBalances();
    }
  }, [autoLoad, isAvailable, availableTokens.length, loadBalances]);

  // Deprecated: Fallback polling interval (prefer wallet events)
  // Kept for backward compatibility and edge cases where events might not work
  useEffect(() => {
    if (refreshInterval <= 0 || !isAvailable) return;

    const intervalId = setInterval(() => {
      loadBalances();
    }, refreshInterval);

    return () => {
      clearInterval(intervalId);
    };
  }, [refreshInterval, isAvailable, loadBalances]);

  // Wallet events refresh: listen for real-time blockchain events
  // Refreshes on ANY wallet domain event (Transfer, TransferSingle, etc.)
  // Debouncing prevents excessive API calls during rapid events
  useEffect(() => {
    if (!sdk || !refreshOnWalletEvents || !isAvailable) return;

    const unsubscribe = sdk.events.subscribe((event) => {
      if (event.domain === 'wallet') {
        // Debounce rapid events (batch transfers, etc.)
        if (walletEventDebounceRef.current) {
          clearTimeout(walletEventDebounceRef.current);
        }
        
        walletEventDebounceRef.current = setTimeout(() => {
          console.log(`[useTokenBalances] Wallet event (${event.type}), refreshing balances...`);
          loadBalances();
          walletEventDebounceRef.current = null;
        }, walletEventDebounceMs);
      }
    }, { domains: ['wallet'] });

    return () => {
      unsubscribe();
      if (walletEventDebounceRef.current) {
        clearTimeout(walletEventDebounceRef.current);
      }
    };
  }, [sdk, refreshOnWalletEvents, walletEventDebounceMs, isAvailable, loadBalances]);

  return {
    tokenBalances,
    isLoading,
    error,
    refresh: loadBalances,
    isAvailable,
    userAccountAddress: accountAddress
  };
}
