import { useCallback, useMemo } from 'react';
import { usePersSDK } from '../providers/PersSDKProvider';
import { Web3Manager } from '@explorins/pers-sdk/web3';
import type { 
  TokenBalance, 
  TokenBalanceRequest, 
  TokenCollectionRequest, 
  TokenCollection,
  TokenMetadata,
  AccountOwnedTokensResult
} from '@explorins/pers-sdk/web3';
import type { TokenDTO } from '@explorins/pers-sdk/token';

// ChainData type - matches what Web3Manager.getChainDataById returns
type ChainData = Awaited<ReturnType<Web3Manager['getChainDataById']>>;

// Re-export for convenience
export type { AccountOwnedTokensResult } from '@explorins/pers-sdk/web3';

/**
 * React hook for Web3 operations in the PERS SDK
 * 
 * Provides comprehensive Web3 functionality including token balance queries,
 * metadata retrieval, collection management, IPFS resolution, and blockchain
 * explorer integration. Supports multi-chain operations.
 * 
 * Note: Wallet addresses should be obtained from `user.wallets` via `usePersSDK()`.
 * 
 * @returns Web3 hook with methods for blockchain operations
 * 
 * @example
 * ```typescript
 * function Web3Component() {
 *   const { user } = usePersSDK();
 *   const { getTokenBalance, getTokenMetadata } = useWeb3();
 *   
 *   // Get wallet address from user
 *   const walletAddress = user?.wallets?.[0]?.address;
 *   
 *   const loadTokenData = async () => {
 *     if (!walletAddress) {
 *       console.log('No wallet connected');
 *       return;
 *     }
 *     
 *     const balance = await getTokenBalance({
 *       walletAddress,
 *       contractAddress: '0x123...',
 *       chainId: 1
 *     });
 *     
 *     console.log('Token balance:', balance);
 *   };
 * 
 *   return (
 *     <View>
 *       {walletAddress ? (
 *         <View>
 *           <Text>Wallet: {walletAddress}</Text>
 *           <Button onPress={loadTokenData} title="Load Tokens" />
 *         </View>
 *       ) : (
 *         <Text>No wallet connected</Text>
 *       )}
 *     </View>
 *   );
 * }
 * ```
 */
export const useWeb3 = () => {
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();

  if (!isAuthenticated && isInitialized) {
    console.warn('SDK not authenticated. Some web3 operations may fail.');
  }

  /**
   * Create Web3Manager instance lazily
   * Web3Manager is now separate from PersSDK since v2.0.11+
   */
  const web3 = useMemo(() => {
    if (!sdk) return null;
    return new Web3Manager(sdk.api());
  }, [sdk]);

  /**
   * Retrieves token balance for a specific wallet and contract
   * 
   * @param request - Token balance request containing wallet address, contract, and chain info
   * @returns Promise resolving to token balance data
   * @throws Error if SDK is not initialized
   * 
   * @example
   * ```typescript
   * const { getTokenBalance } = useWeb3();
   * const balance = await getTokenBalance({
   *   walletAddress: '0x123...',
   *   contractAddress: '0x456...',
   *   chainId: 1,
   *   tokenId: '1' // For NFTs
   * });
   * console.log('Balance:', balance.balance);
   * ```
   */
  const getTokenBalance = useCallback(async (request: TokenBalanceRequest): Promise<TokenBalance> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.getTokenBalance(request);
      return result;
    } catch (error) {
      console.error('Failed to fetch token balance:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  /**
   * Retrieves metadata for a specific token (useful for NFTs)
   * 
   * @param request - Token balance request containing contract and token info
   * @returns Promise resolving to token metadata or null if not available
   * @throws Error if SDK is not initialized
   * 
   * @example
   * ```typescript
   * const { getTokenMetadata } = useWeb3();
   * const metadata = await getTokenMetadata({
   *   contractAddress: '0x123...',
   *   tokenId: '1',
   *   chainId: 1
   * });
   * console.log('NFT name:', metadata?.name);
   * console.log('NFT image:', metadata?.imageUrl); // IPFS URLs auto-resolved to HTTPS
   * ```
   */
  const getTokenMetadata = useCallback(async (request: TokenBalanceRequest): Promise<TokenMetadata | null> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.getTokenMetadata(request);
      return result;
    } catch (error) {
      console.error('Failed to fetch token metadata:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  const getTokenCollection = useCallback(async (request: TokenCollectionRequest): Promise<TokenCollection> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.getTokenCollection(request);
      return result;
    } catch (error) {
      console.error('Failed to fetch token collection:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  /**
   * Resolve IPFS URL to HTTP gateway URL
   * 
   * @deprecated Use `sdk.tenant.resolveIPFSUrl()` directly - IPFS is chain-agnostic
   * @param url - IPFS URL to resolve (ipfs://...)
   * @returns Promise resolving to HTTP gateway URL
   */
  const resolveIPFSUrl = useCallback(async (url: string): Promise<string> => {
    if (!isInitialized || !sdk) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await sdk.tenants.resolveIPFSUrl(url);
      return result;
    } catch (error) {
      console.error('Failed to resolve IPFS URL:', error);
      throw error;
    }
  }, [sdk, isInitialized]);

  /**
   * Fetch and process token metadata from a URI
   * 
   * @param tokenUri - Token URI to fetch metadata from
   * @returns Promise resolving to processed metadata or null
   */
  const fetchAndProcessMetadata = useCallback(async (tokenUri: string): Promise<TokenMetadata | null> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.fetchAndProcessMetadata(tokenUri);
      return result;
    } catch (error) {
      console.error('Failed to fetch and process metadata:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  const getChainDataById = useCallback(async (chainId: number): Promise<ChainData> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.getChainDataById(chainId);
      return result;
    } catch (error) {
      console.error('Failed to fetch chain data:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  const getExplorerUrl = useCallback(async (chainId: number, address: string, type: 'address' | 'tx'): Promise<string> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const result = await web3.getExplorerUrl(chainId, address, type);
      return result;
    } catch (error) {
      console.error('Failed to generate explorer URL:', error);
      throw error;
    }
  }, [web3, isInitialized]);

  // ==========================================
  // HELPER METHODS (delegating to core SDK)
  // ==========================================

  /**
   * Extract tokenIds from a TokenDTO's metadata.
   * 
   * Extracts `tokenMetadataIncrementalId` from each metadata entry. This is
   * particularly useful for ERC-1155 tokens which require specific tokenIds
   * to query balances, but works with any token that has metadata.
   * 
   * **Note:** For most use cases, prefer `getAccountOwnedTokensFromContract()`
   * which handles tokenId extraction automatically.
   * 
   * @param token - Token definition containing metadata array
   * @returns Array of tokenId strings, or undefined if no metadata
   * 
   * @see {@link getAccountOwnedTokensFromContract} - Recommended helper that handles this automatically
   */
  const extractTokenIds = useCallback((token: TokenDTO): string[] | undefined => {
    // Pure function - delegates to Web3Manager (no initialization required)
    return web3?.extractTokenIds(token);
  }, [web3]);

  /**
   * Get owned tokens from a specific token contract for any blockchain address.
   * 
   * **Recommended method** for querying token balances. Automatically handles:
   * - Token type detection (ERC-20, ERC-721, ERC-1155)
   * - TokenId extraction from metadata (uses `extractTokenIds()` internally for ERC-1155)
   * - Building the collection request
   * - Filtering to only tokens with balance > 0
   * 
   * Works with any valid blockchain address - can query user wallets, external
   * wallets, contract addresses, or any other address holding tokens.
   * 
   * @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
   * @param token - Token definition (from getRewardTokens, getStatusTokens, etc.)
   * @param maxTokens - Maximum tokens to retrieve (default: 100, max: 100)
   * @returns Promise resolving to result with owned tokens
   * @throws Error if SDK is not initialized
   * 
   * @example Query user's wallet
   * ```typescript
   * const { user } = usePersSDK();
   * const { getAccountOwnedTokensFromContract } = useWeb3();
   * const { getRewardTokens } = useTokens();
   * 
   * const userWallet = user?.wallets?.[0]?.address;
   * const rewardTokens = await getRewardTokens();
   * const result = await getAccountOwnedTokensFromContract(userWallet, rewardTokens[0]);
   * console.log(`User owns ${result.totalOwned} tokens`);
   * ```
   * 
   * @see {@link extractTokenIds} - Low-level helper used internally for ERC-1155
   * @see {@link buildCollectionRequest} - For manual request building
   */
  const getAccountOwnedTokensFromContract = useCallback(async (
    accountAddress: string,
    token: TokenDTO,
    maxTokens: number = 100
  ): Promise<AccountOwnedTokensResult> => {
    if (!isInitialized || !web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }

    return web3.getAccountOwnedTokensFromContract(accountAddress, token, maxTokens);
  }, [web3, isInitialized]);

  /**
   * Build a TokenCollectionRequest from a TokenDTO.
   * 
   * Automatically handles ERC-1155 tokenId extraction from metadata.
   * Use this when you need more control over the request than 
   * `getAccountOwnedTokensFromContract` provides.
   * 
   * @param accountAddress - Any valid blockchain address (wallet, contract, etc.)
   * @param token - Token definition
   * @param maxTokens - Maximum tokens to retrieve (default: 100, max: 100)
   * @returns TokenCollectionRequest ready for getTokenCollection()
   */
  const buildCollectionRequest = useCallback((
    accountAddress: string,
    token: TokenDTO,
    maxTokens: number = 100
  ): TokenCollectionRequest => {
    if (!web3) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    return web3.buildCollectionRequest(accountAddress, token, maxTokens);
  }, [web3]);

  return {
    getTokenBalance,
    getTokenMetadata,
    getTokenCollection,
    resolveIPFSUrl,
    fetchAndProcessMetadata,
    getChainDataById,
    getExplorerUrl,
    // Helper methods
    extractTokenIds,
    getAccountOwnedTokensFromContract,
    buildCollectionRequest,
    isAvailable: isInitialized && !!web3,
  };
};

export type Web3Hook = ReturnType<typeof useWeb3>;