import { validateMetadata, saveCollectionData, fetchWithRetry } from './base-fetcher.js';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import axios from 'axios';
import dotenv from 'dotenv';
import { NFTMetadata, NFTCollection } from '../types/nft.js';

// Load environment variables
dotenv.config();

const ETH_NODE_URL = process.env.RPC_ENDPOINT;
const CONTRACT_ADDRESS = process.env.NFT_CONTRACT_ADDRESS;

// Validate required environment variables
if (!ETH_NODE_URL || !CONTRACT_ADDRESS) {
  console.error('Error: Missing required environment variables. Please check your .env file.');
  process.exit(1);
}

// Ethereum configuration
const ERC721_ABI = [
  { name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'tokenURI', type: 'function', stateMutability: 'view', inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [{ type: 'string' }] },
];

const client = createPublicClient({
  chain: mainnet,
  transport: http(ETH_NODE_URL)
});

// Get total supply of tokens
const getTotalSupply = async (contractAddress: string): Promise<bigint> => {
  try {
    return await client.readContract({
      address: contractAddress,
      abi: ERC721_ABI,
      functionName: 'totalSupply'
    });
  } catch (error) {
    console.log('Failed to get totalSupply, using binary search...');
    return findLastTokenId(contractAddress);
  }
};

// Binary search to find last valid token ID
const findLastTokenId = async (contractAddress: string): Promise<bigint> => {
  let left = 1n;
  let right = 10000n;
  let lastValidId = 0n;

  while (left <= right) {
    const mid = (left + right) / 2n;
    try {
      await client.readContract({
        address: contractAddress,
        abi: ERC721_ABI,
        functionName: 'tokenURI',
        args: [mid]
      });
      lastValidId = mid;
      left = mid + 1n;
    } catch (_error) {
      right = mid - 1n;
    }
  }

  return lastValidId;
};

// Fetch metadata for a single token
const fetchTokenMetadata = async (contractAddress: string, tokenId: bigint): Promise<NFTMetadata> => {
  const uri = await client.readContract({
    address: contractAddress,
    abi: ERC721_ABI,
    functionName: 'tokenURI',
    args: [tokenId]
  });

  const metadataUrl = uri.startsWith('ipfs://') 
    ? uri.replace('ipfs://', 'https://ipfs.io/ipfs/')
    : uri;

  const response = await fetchWithRetry(() => 
    axios.get(metadataUrl, { timeout: 10000 })
  );

  const rawMetadata = response.data;
  
  // Transform to standard format
  return {
    name: rawMetadata.name,
    description: rawMetadata.description || '',
    image: rawMetadata.image,
    animation_url: rawMetadata.animation_url,
    external_url: rawMetadata.external_url,
    attributes: Array.isArray(rawMetadata.attributes) 
      ? rawMetadata.attributes.map(attr => ({
          trait_type: attr.trait_type || attr.traitType || 'Unknown',
          value: String(attr.value)
        }))
      : []
  };
};

// Main function to fetch collection data
const fetchCollectionData = async (contractAddress: string): Promise<NFTCollection> => {
  const supply = await getTotalSupply(contractAddress);
  console.log(`Found ${supply.toString()} tokens in collection`);

  const collection: NFTCollection = [];
  for (let tokenId = 1n; tokenId <= supply; tokenId++) {
    try {
      const metadata = await fetchTokenMetadata(contractAddress, tokenId);
      collection.push(metadata);
      console.log(`Fetched metadata for token ${tokenId.toString()}`);
    } catch (error) {
      console.warn(`⚠️ Skipped token ${tokenId.toString()}: ${error}`);
    }
  }

  await saveCollectionData(collection);
  return collection;
};

// Run the fetcher
(async () => {
  try {
    await fetchCollectionData(CONTRACT_ADDRESS);
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
})(); 