#!/usr/bin/env node
/**
 * Enhanced Token List Validator
 * Validates token lists against the Uniswap Token Lists JSON schema
 * Based on: https://github.com/Uniswap/token-lists
 * Uses: @uniswap/token-lists package for schema and types
 * 
 * Enhanced with:
 * - EIP-55 checksum validation
 * - Duplicate detection
 * - Logo URL validation
 * - Chain ID strict validation
 * - Semantic versioning validation
 */

import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { ethers } from 'ethers';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Required chain ID (optional - if not set, will validate all tokens have same chainId)
// Can be overridden via --chain-id flag
let REQUIRED_CHAIN_ID = null;

/**
 * Get schema from @uniswap/token-lists package
 * Falls back to fetching from URL if package not available
 */
async function getSchema() {
  try {
    // Try to import schema from @uniswap/token-lists package
    const tokenLists = await import('@uniswap/token-lists');
    if (tokenLists.schema) {
      console.log('✅ Using schema from @uniswap/token-lists package\n');
      return tokenLists.schema;
    }
  } catch (error) {
    console.log('⚠️  @uniswap/token-lists package not available, fetching schema from URL...\n');
  }
  
  // Fallback: fetch schema from Uniswap
  try {
    const SCHEMA_URL = 'https://uniswap.org/tokenlist.schema.json';
    const response = await fetch(SCHEMA_URL);
    if (!response.ok) {
      throw new Error(`Failed to fetch schema: ${response.statusText}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Error fetching schema:', error.message);
    console.error('Falling back to basic validation...');
    return null;
  }
}

// Validate EIP-55 checksum
function isChecksummed(address) {
  try {
    return ethers.isAddress(address) && address === ethers.getAddress(address);
  } catch {
    return false;
  }
}

// Basic validation with enhanced checks
function enhancedValidation(tokenList) {
  const errors = [];
  const warnings = [];
  const seenAddresses = new Set();
  const seenSymbols = new Map(); // chainId -> Set of symbols
  let detectedChainId = null;

  // Required fields
  if (!tokenList.name || typeof tokenList.name !== 'string') {
    errors.push('Missing or invalid "name" field');
  }

  if (!tokenList.version) {
    errors.push('Missing "version" field');
  } else {
    if (typeof tokenList.version.major !== 'number') {
      errors.push('version.major must be a number');
    }
    if (typeof tokenList.version.minor !== 'number') {
      errors.push('version.minor must be a number');
    }
    if (typeof tokenList.version.patch !== 'number') {
      errors.push('version.patch must be a number');
    }
  }

  if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
    errors.push('Missing or invalid "tokens" array');
    return { errors, warnings, valid: false };
  }

  // Detect chain ID from first token if not specified
  if (tokenList.tokens.length > 0 && tokenList.tokens[0].chainId) {
    detectedChainId = tokenList.tokens[0].chainId;
  }

  // Validate each token
  tokenList.tokens.forEach((token, index) => {
    const prefix = `Token[${index}]`;
    
    // Required token fields
    if (typeof token.chainId !== 'number') {
      errors.push(`${prefix}: Missing or invalid "chainId"`);
    } else {
      // Chain ID consistency check
      if (detectedChainId === null) {
        detectedChainId = token.chainId;
      } else if (token.chainId !== detectedChainId) {
        errors.push(`${prefix}: chainId mismatch - expected ${detectedChainId}, got ${token.chainId}`);
      }
      
      // Strict chain ID validation (if REQUIRED_CHAIN_ID is set)
      if (REQUIRED_CHAIN_ID !== null && token.chainId !== REQUIRED_CHAIN_ID) {
        errors.push(`${prefix}: chainId must be ${REQUIRED_CHAIN_ID}, got ${token.chainId}`);
      }
    }
    
    if (!token.address || typeof token.address !== 'string') {
      errors.push(`${prefix}: Missing or invalid "address"`);
    } else {
      // Validate Ethereum address format
      if (!/^0x[a-fA-F0-9]{40}$/.test(token.address)) {
        errors.push(`${prefix}: Invalid Ethereum address format: ${token.address}`);
      } else {
        // EIP-55 checksum validation
        if (!isChecksummed(token.address)) {
          errors.push(`${prefix}: Address not EIP-55 checksummed: ${token.address}`);
        }
        
        // Duplicate address detection
        const addressLower = token.address.toLowerCase();
        if (seenAddresses.has(addressLower)) {
          errors.push(`${prefix}: Duplicate address: ${token.address}`);
        }
        seenAddresses.add(addressLower);
      }
    }
    
    if (!token.name || typeof token.name !== 'string') {
      errors.push(`${prefix}: Missing or invalid "name"`);
    }
    
    if (!token.symbol || typeof token.symbol !== 'string') {
      errors.push(`${prefix}: Missing or invalid "symbol"`);
    } else {
      // Symbol uniqueness per chainId
      const chainId = token.chainId || 0;
      if (!seenSymbols.has(chainId)) {
        seenSymbols.set(chainId, new Set());
      }
      const symbolSet = seenSymbols.get(chainId);
      if (symbolSet.has(token.symbol.toUpperCase())) {
        warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`);
      }
      symbolSet.add(token.symbol.toUpperCase());
    }
    
    if (typeof token.decimals !== 'number' || token.decimals < 0 || token.decimals > 255) {
      errors.push(`${prefix}: Invalid "decimals" (must be 0-255), got ${token.decimals}`);
    }

    // Optional fields (warnings)
    if (!token.logoURI) {
      warnings.push(`${prefix}: Missing "logoURI" (optional but recommended)`);
    } else if (typeof token.logoURI !== 'string') {
      warnings.push(`${prefix}: Invalid "logoURI" type`);
    } else if (!token.logoURI.startsWith('http://') && 
               !token.logoURI.startsWith('https://') && 
               !token.logoURI.startsWith('ipfs://')) {
      warnings.push(`${prefix}: Invalid "logoURI" format (should be HTTP/HTTPS/IPFS URL): ${token.logoURI}`);
    } else if (!token.logoURI.startsWith('https://') && !token.logoURI.startsWith('ipfs://')) {
      warnings.push(`${prefix}: logoURI should use HTTPS (not HTTP): ${token.logoURI}`);
    }
  });

  return { errors, warnings, valid: errors.length === 0 };
}

async function validateTokenList(filePath) {
  console.log(`\n🔍 Validating token list: ${filePath}\n`);

  // Read token list file
  let tokenList;
  try {
    const fileContent = readFileSync(filePath, 'utf-8');
    tokenList = JSON.parse(fileContent);
  } catch (error) {
    console.error('❌ Error reading or parsing token list file:');
    console.error(`   ${error.message}`);
    process.exit(1);
  }

  // Get schema from @uniswap/token-lists package or fetch from URL
  const schema = await getSchema();
  let validationResult;

  if (schema) {
    // Use AJV if available, otherwise fall back to enhanced validation
    try {
      // Try to use dynamic import for ajv (if installed)
      const { default: Ajv } = await import('ajv');
      const addFormats = (await import('ajv-formats')).default;
      
      const ajv = new Ajv({ allErrors: true, verbose: true });
      addFormats(ajv);
      const validate = ajv.compile(schema);
      const valid = validate(tokenList);

      if (valid) {
        // Schema validation passed, now run enhanced checks
        validationResult = enhancedValidation(tokenList);
      } else {
        const schemaErrors = validate.errors?.map(err => {
          const path = err.instancePath || err.schemaPath || '';
          return `${path}: ${err.message}`;
        }) || [];
        const enhanced = enhancedValidation(tokenList);
        validationResult = {
          errors: [...schemaErrors, ...enhanced.errors],
          warnings: enhanced.warnings,
          valid: false
        };
      }
    } catch (importError) {
      // AJV not available, use enhanced validation
      console.log('⚠️  AJV not available, using enhanced validation');
      validationResult = enhancedValidation(tokenList);
    }
  } else {
    // Schema fetch failed, use enhanced validation
    validationResult = enhancedValidation(tokenList);
  }

  // Display results
  if (validationResult.valid) {
    console.log('✅ Token list is valid!\n');
    
    // Display token list info
    console.log('📋 Token List Info:');
    console.log(`   Name: ${tokenList.name}`);
    if (tokenList.version) {
      console.log(`   Version: ${tokenList.version.major}.${tokenList.version.minor}.${tokenList.version.patch}`);
    }
    if (tokenList.timestamp) {
      console.log(`   Timestamp: ${tokenList.timestamp}`);
    }
    console.log(`   Tokens: ${tokenList.tokens.length}`);
    console.log('');
    
    // List tokens
    console.log('🪙 Tokens:');
    tokenList.tokens.forEach((token, index) => {
      console.log(`   ${index + 1}. ${token.symbol} (${token.name})`);
      console.log(`      Address: ${token.address}`);
      console.log(`      Chain ID: ${token.chainId}`);
      console.log(`      Decimals: ${token.decimals}`);
      if (token.logoURI) {
        console.log(`      Logo: ${token.logoURI}`);
      }
      console.log('');
    });

    if (validationResult.warnings.length > 0) {
      console.log('⚠️  Warnings:');
      validationResult.warnings.forEach(warning => {
        console.log(`   - ${warning}`);
      });
      console.log('');
    }
    
    process.exit(0);
  } else {
    console.error('❌ Token list validation failed!\n');
    
    if (validationResult.errors.length > 0) {
      console.error('Errors:');
      validationResult.errors.forEach(error => {
        console.error(`   ❌ ${error}`);
      });
      console.log('');
    }
    
    if (validationResult.warnings.length > 0) {
      console.log('Warnings:');
      validationResult.warnings.forEach(warning => {
        console.log(`   ⚠️  ${warning}`);
      });
      console.log('');
    }
    
    process.exit(1);
  }
}

// Main
const args = process.argv.slice(2);
const filePath = args.find(arg => !arg.startsWith('--')) || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
const chainIdArg = args.find(arg => arg.startsWith('--chain-id='));

if (chainIdArg) {
  REQUIRED_CHAIN_ID = parseInt(chainIdArg.split('=')[1], 10);
}

if (!filePath) {
  console.error('Usage: node validate-token-list.js [path/to/token-list.json] [--chain-id=138]');
  process.exit(1);
}

validateTokenList(filePath).catch(error => {
  console.error('Unexpected error:', error);
  process.exit(1);
});

