/**
 * Trustless Bridge Challenger Service
 * 
 * Monitors ClaimSubmitted events from InboxETH on Ethereum Mainnet
 * Verifies claims against source chain (ChainID 138) state
 * Submits challenges with fraud proofs when invalid claims are detected
 * 
 * Permissionless: Anyone can run this service to challenge fraudulent claims
 */

const { ethers } = require('ethers');
require('dotenv').config();

// Configuration
const CONFIG = {
  // ChainID 138 (Besu)
  CHAIN138_RPC: process.env.CHAIN138_RPC_URL || 'https://rpc.d-bis.org',
  LOCKBOX138_ADDRESS: process.env.LOCKBOX138_ADDRESS,
  
  // Ethereum Mainnet
  ETHEREUM_RPC: process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com',
  INBOX_ETH_ADDRESS: process.env.INBOX_ETH_ADDRESS,
  CHALLENGE_MANAGER_ADDRESS: process.env.CHALLENGE_MANAGER_ADDRESS,
  
  // Challenger configuration
  CHALLENGER_PRIVATE_KEY: process.env.CHALLENGER_PRIVATE_KEY,
  
  // Polling interval (milliseconds)
  POLL_INTERVAL: parseInt(process.env.POLL_INTERVAL || '10000'), // 10 seconds
};

// Lockbox138 ABI
const LOCKBOX138_ABI = [
  "function isDepositProcessed(uint256 depositId) external view returns (bool)",
  "function getNonce(address user) external view returns (uint256)"
];

// InboxETH ABI
const INBOX_ETH_ABI = [
  "event ClaimSubmitted(uint256 indexed depositId, address indexed relayer, address asset, uint256 amount, address indexed recipient, uint256 bondAmount, uint256 challengeWindowEnd)",
  "function getClaimStatus(uint256 depositId) external view returns (bool exists, bool finalized, bool challenged, uint256 challengeWindowEnd)",
  "function getClaim(uint256 depositId) external view returns (tuple(uint256 depositId, address asset, uint256 amount, address recipient, address relayer, uint256 timestamp, bool exists))"
];

// ChallengeManager ABI
const CHALLENGE_MANAGER_ABI = [
  "function challengeClaim(uint256 depositId, uint8 proofType, bytes calldata proof) external",
  "function getClaim(uint256 depositId) external view returns (tuple(uint256 depositId, address asset, uint256 amount, address recipient, uint256 challengeWindowEnd, bool finalized, bool challenged))",
  "function canFinalize(uint256 depositId) external view returns (bool canFinalize, string memory reason)"
];

class TrustlessBridgeChallenger {
  constructor() {
    // Initialize providers
    this.chain138Provider = new ethers.JsonRpcProvider(CONFIG.CHAIN138_RPC);
    this.ethereumProvider = new ethers.JsonRpcProvider(CONFIG.ETHEREUM_RPC);
    
    // Initialize wallet (challenger)
    if (!CONFIG.CHALLENGER_PRIVATE_KEY) {
      throw new Error('CHALLENGER_PRIVATE_KEY environment variable is required');
    }
    this.wallet = new ethers.Wallet(CONFIG.CHALLENGER_PRIVATE_KEY, this.ethereumProvider);
    
    // Initialize contracts
    this.lockbox138 = new ethers.Contract(CONFIG.LOCKBOX138_ADDRESS, LOCKBOX138_ABI, this.chain138Provider);
    this.inboxETH = new ethers.Contract(CONFIG.INBOX_ETH_ADDRESS, INBOX_ETH_ABI, this.ethereumProvider);
    this.challengeManager = new ethers.Contract(CONFIG.CHALLENGE_MANAGER_ADDRESS, CHALLENGE_MANAGER_ABI, this.wallet);
    
    // Track processed claims
    this.processedClaims = new Set();
  }

  async start() {
    console.log('Starting Trustless Bridge Challenger...');
    console.log('Challenger Address:', this.wallet.address);
    console.log('ChainID 138 RPC:', CONFIG.CHAIN138_RPC);
    console.log('Ethereum RPC:', CONFIG.ETHEREUM_RPC);
    console.log('InboxETH:', CONFIG.INBOX_ETH_ADDRESS);
    console.log('ChallengeManager:', CONFIG.CHALLENGE_MANAGER_ADDRESS);
    
    // Check challenger balance
    await this.checkBalance();
    
    // Start monitoring
    this.monitorClaims();
  }

  async checkBalance() {
    const balance = await this.ethereumProvider.getBalance(this.wallet.address);
    console.log('Challenger Balance:', ethers.formatEther(balance), 'ETH');
    
    if (balance < ethers.parseEther('0.1')) {
      console.warn('Warning: Low balance for challenging claims (need ETH for gas)');
    }
  }

  async monitorClaims() {
    console.log('Monitoring ClaimSubmitted events from InboxETH...');
    
    // Listen for new ClaimSubmitted events
    this.inboxETH.on('ClaimSubmitted', async (depositId, relayer, asset, amount, recipient, bondAmount, challengeWindowEnd, event) => {
      try {
        await this.handleClaim(depositId, relayer, asset, amount, recipient, bondAmount, challengeWindowEnd);
      } catch (error) {
        console.error('Error handling claim:', error);
      }
    });

    // Also poll for past events
    setInterval(async () => {
      try {
        await this.pollPastClaims();
      } catch (error) {
        console.error('Error polling claims:', error);
      }
    }, CONFIG.POLL_INTERVAL);
  }

  async pollPastClaims() {
    // Get recent blocks
    const currentBlock = await this.ethereumProvider.getBlockNumber();
    const fromBlock = Math.max(currentBlock - 100, 0);
    
    const filter = this.inboxETH.filters.ClaimSubmitted();
    const events = await this.inboxETH.queryFilter(filter, fromBlock, currentBlock);
    
    for (const event of events) {
      const { depositId, relayer, asset, amount, recipient, bondAmount, challengeWindowEnd } = event.args;
      await this.handleClaim(depositId, relayer, asset, amount, recipient, bondAmount, challengeWindowEnd);
    }
  }

  async handleClaim(depositId, relayer, asset, amount, recipient, bondAmount, challengeWindowEnd) {
    const depositIdStr = depositId.toString();
    
    // Skip if already processed
    if (this.processedClaims.has(depositIdStr)) {
      return;
    }

    console.log(`\n=== New Claim Detected ===`);
    console.log('Deposit ID:', depositIdStr);
    console.log('Relayer:', relayer);
    console.log('Asset:', asset);
    console.log('Amount:', ethers.formatEther(amount), 'ETH');
    console.log('Recipient:', recipient);
    console.log('Bond Amount:', ethers.formatEther(bondAmount), 'ETH');
    console.log('Challenge Window End:', new Date(Number(challengeWindowEnd) * 1000).toISOString());

    // Check if challenge window is still open
    const now = Math.floor(Date.now() / 1000);
    if (Number(challengeWindowEnd) <= now) {
      console.log('Challenge window expired, skipping...');
      this.processedClaims.add(depositIdStr);
      return;
    }

    // Verify claim against source chain
    try {
      const isValid = await this.verifyClaim(depositId, asset, amount, recipient);
      
      if (!isValid) {
        console.log('Invalid claim detected! Submitting challenge...');
        await this.submitChallenge(depositId);
        this.processedClaims.add(depositIdStr);
      } else {
        console.log('Claim is valid, no challenge needed');
        this.processedClaims.add(depositIdStr);
      }
    } catch (error) {
      console.error('Error verifying claim:', error);
    }
  }

  async verifyClaim(depositId, asset, amount, recipient) {
    console.log('Verifying claim against source chain...');
    
    // Check if deposit exists on source chain
    // In production, this would use Merkle proofs or light client verification
    // For now, we use a simple check: verify deposit was processed
    
    try {
      const exists = await this.lockbox138.isDepositProcessed(depositId);
      if (!exists) {
        console.log('Deposit does not exist on source chain!');
        return false;
      }
      
      // Additional verification could include:
      // - Check amount matches
      // - Check recipient matches
      // - Check asset matches
      // - Verify Merkle proof
      
      return true;
    } catch (error) {
      console.error('Error checking deposit on source chain:', error);
      return false; // Fail closed - if we can't verify, don't challenge (or challenge conservatively)
    }
  }

  async submitChallenge(depositId) {
    console.log('Submitting challenge...');
    
    // For NonExistentDeposit fraud proof type (0)
    // In production, you would generate a proper fraud proof (Merkle proof, etc.)
    const proofType = 0; // NonExistentDeposit
    const proof = ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [depositId]);
    
    try {
      const tx = await this.challengeManager.challengeClaim(depositId, proofType, proof);
      console.log('Challenge transaction submitted:', tx.hash);
      const receipt = await tx.wait();
      console.log('Challenge confirmed in block:', receipt.blockNumber);
      console.log('Challenge submitted successfully! Bond will be slashed.');
    } catch (error) {
      console.error('Failed to submit challenge:', error);
      throw error;
    }
  }
}

// Start challenger
if (require.main === module) {
  const challenger = new TrustlessBridgeChallenger();
  challenger.start().catch(console.error);
}

module.exports = TrustlessBridgeChallenger;
