/**
 * LOOPUMAN - BLOCKCHAIN WITHDRAWAL SERVICE
 * Handles withdrawals to Celo blockchain in cUSD, USDC, USDT
 * 
 * Uses ethers.js to send stablecoin transactions
 */

'use strict';
const { ethers } = require('ethers');
const { createClient } = require('@supabase/supabase-js');

const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY);

// ERC20 ABI (minimal for transfer)
const ERC20_ABI = [
  'function transfer(address to, uint256 amount) returns (bool)',
  'function balanceOf(address account) view returns (uint256)',
  'function decimals() view returns (uint8)',
];

// Celo Mainnet stablecoin addresses
const STABLECOIN_ADDRESSES = {
  cUSD: '0x765DE816845861e75A25fCA122bb6898B8B1282a',
  USDC: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
  USDT: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
};

const CONFIG = {
  VAE_RATE: 100,              // 100 VAE = $1
  MIN_WITHDRAWAL_VAE: 100,    // Minimum Ⓥ100 ($1)
  RPC_URL: process.env.CELO_RPC_URL || 'https://forno.celo.org',
  TREASURY_KEY: process.env.TREASURY_PRIVATE_KEY,
  
  // Identity tier withdrawal limits (VAE per month)
  TIER_LIMITS: {
    0: 1000,    // $10/month - Telegram only
    1: 10000,   // $100/month - GoodDollar
    2: 50000,   // $500/month - ID+Selfie
    3: null,    // Unlimited - Self Protocol
  },
};

// Initialize provider and wallet
let provider, wallet;

function initBlockchain() {
  if (!CONFIG.TREASURY_KEY) {
    console.error('[Blockchain] Treasury private key not configured!');
    return false;
  }
  
  provider = new ethers.JsonRpcProvider(CONFIG.RPC_URL);
  wallet = new ethers.Wallet(CONFIG.TREASURY_KEY, provider);
  
  console.log(`[Blockchain] Initialized with treasury: ${wallet.address}`);
  return true;
}

/**
 * Check if user can withdraw (tier limits)
 */
async function checkWithdrawalEligibility(userId, amountVae) {
  // Get user
  const { data: user } = await supabase
    .from('app_users')
    .select('identity_tier, wallet_address')
    .eq('id', userId)
    .single();
  
  if (!user) {
    return { eligible: false, reason: 'User not found' };
  }
  
  if (!user.wallet_address) {
    return { eligible: false, reason: 'No wallet address set' };
  }
  
  // Check tier limit
  const limit = CONFIG.TIER_LIMITS[user.identity_tier || 0];
  
  if (limit !== null) {
    // Get monthly withdrawn
    const { data: withdrawals } = await supabase
      .from('withdrawals')
      .select('amount_vae')
      .eq('user_id', userId)
      .eq('status', 'completed')
      .gt('created_at', new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
    
    const monthlyTotal = (withdrawals || []).reduce((sum, w) => sum + w.amount_vae, 0);
    const remaining = limit - monthlyTotal;
    
    if (amountVae > remaining) {
      return {
        eligible: false,
        reason: `Monthly limit exceeded. Remaining: Ⓥ${remaining} (Tier ${user.identity_tier || 0})`,
        remaining,
        limit,
      };
    }
  }
  
  // Check minimum
  if (amountVae < CONFIG.MIN_WITHDRAWAL_VAE) {
    return { eligible: false, reason: `Minimum withdrawal: Ⓥ${CONFIG.MIN_WITHDRAWAL_VAE}` };
  }
  
  return {
    eligible: true,
    wallet: user.wallet_address,
    tier: user.identity_tier || 0,
  };
}

/**
 * Get treasury balance for a stablecoin
 */
async function getTreasuryBalance(coin) {
  if (!provider || !wallet) {
    initBlockchain();
  }
  
  const address = STABLECOIN_ADDRESSES[coin];
  if (!address) {
    throw new Error(`Unknown stablecoin: ${coin}`);
  }
  
  const contract = new ethers.Contract(address, ERC20_ABI, provider);
  const balance = await contract.balanceOf(wallet.address);
  const decimals = await contract.decimals();
  
  return parseFloat(ethers.formatUnits(balance, decimals));
}

/**
 * Process withdrawal
 */
async function processWithdrawal(userId, amountVae, stablecoin) {
  // Validate stablecoin
  if (!STABLECOIN_ADDRESSES[stablecoin]) {
    throw new Error(`Invalid stablecoin: ${stablecoin}`);
  }
  
  // Check eligibility
  const eligibility = await checkWithdrawalEligibility(userId, amountVae);
  if (!eligibility.eligible) {
    throw new Error(eligibility.reason);
  }
  
  // Check user balance AND deduct atomically
  // Try safe_deduct_balance RPC first (atomic), fallback to read-then-write
  let deducted = false;
  const { data: rpcResult, error: rpcError } = await supabase.rpc('safe_deduct_balance', {
    p_user_id: userId,
    p_amount: amountVae,
  });
  
  if (rpcError) {
    // Fallback: read-check-write
    console.warn(`[Withdrawal] safe_deduct_balance RPC unavailable, using fallback`);
    const { data: balance } = await supabase
      .from('vae_balances')
      .select('available, total_withdrawn')
      .eq('user_id', userId)
      .single();
    
    if (!balance || balance.available < amountVae) {
      throw new Error('Insufficient balance');
    }
    
    const { error: updateErr } = await supabase
      .from('vae_balances')
      .update({
        available: balance.available - amountVae,
        total_withdrawn: (balance.total_withdrawn || 0) + amountVae,
        updated_at: new Date().toISOString(),
      })
      .eq('user_id', userId);
    
    if (updateErr) {
      console.error(`[Withdrawal] Deduction update FAILED:`, updateErr);
      throw new Error('Failed to deduct balance');
    }
    
    // Verify the deduction actually took effect
    const { data: verify } = await supabase
      .from('vae_balances')
      .select('available')
      .eq('user_id', userId)
      .single();
    
    if (verify && verify.available > balance.available) {
      console.error(`[Withdrawal] CRITICAL: Deduction did not take effect! Before: ${balance.available}, After: ${verify.available}`);
      throw new Error('Balance deduction failed verification');
    }
    
    deducted = true;
    console.log(`[Withdrawal] Fallback deduction: ${balance.available} -> ${balance.available - amountVae} (user ${userId})`);
  } else {
    if (!rpcResult) throw new Error('Insufficient balance');
    deducted = true;
    // RPC only deducts available — update total_withdrawn separately for withdrawals
    const { data: curBal } = await supabase.from('vae_balances').select('total_withdrawn').eq('user_id', userId).single();
    await supabase.from('vae_balances').update({
      total_withdrawn: (curBal?.total_withdrawn || 0) + amountVae,
    }).eq('user_id', userId);
    console.log(`[Withdrawal] RPC deduction successful (user ${userId}, amount ${amountVae})`);
  }
  
  const amountUsd = amountVae / CONFIG.VAE_RATE;
  
  // Create withdrawal record
  const { data: withdrawal, error } = await supabase
    .from('withdrawals')
    .insert({
      user_id: userId,
      amount_vae: amountVae,
      amount_usd: amountUsd,
      stablecoin,
      wallet_address: eligibility.wallet,
      status: 'pending',
    })
    .select()
    .single();
  
  if (error) {
    throw new Error('Failed to create withdrawal record');
  }
  
  // Balance already deducted above (atomically)
  
  // Record transaction
  await supabase.from('transactions').insert({
    user_id: userId,
    type: 'withdrawal',
    amount: -amountVae,
    description: `Withdrawal: $${amountUsd} ${stablecoin}`,
    metadata: { withdrawal_id: withdrawal.id },
  });
  
  // Process blockchain transfer (SYNCHRONOUS - wait for result to tell user)
  let txResult = null;
  try {
    txResult = await processBlockchainTransfer(withdrawal.id, eligibility.wallet, amountUsd, stablecoin);
  } catch (err) {
    console.error('[Blockchain] Transfer error:', err);
    // markWithdrawalFailed already called inside processBlockchainTransfer
  }
  
  return {
    withdrawalId: withdrawal.id,
    amountVae,
    amountUsd,
    stablecoin,
    wallet: eligibility.wallet,
    status: txResult ? 'completed' : 'failed',
    txHash: txResult || null,
    failureReason: txResult ? null : 'Blockchain transfer failed',
  };
}

/**
 * Process the actual blockchain transfer
 */
async function processBlockchainTransfer(withdrawalId, toAddress, amountUsd, stablecoin) {
  if (!provider || !wallet) {
    if (!initBlockchain()) {
      await markWithdrawalFailed(withdrawalId, 'Blockchain not configured');
      return;
    }
  }
  
  try {
    const contractAddress = STABLECOIN_ADDRESSES[stablecoin];
    const contract = new ethers.Contract(contractAddress, ERC20_ABI, wallet);
    
    // Get decimals
    const decimals = await contract.decimals();
    const amount = ethers.parseUnits(amountUsd.toString(), decimals);
    
    // Check treasury balance
    const treasuryBalance = await contract.balanceOf(wallet.address);
    if (treasuryBalance < amount) {
      await markWithdrawalFailed(withdrawalId, 'Insufficient treasury balance');
      // Alert admin
      await supabase.from('system_alerts').insert({
        service: 'blockchain',
        level: 'critical',
        message: `Low ${stablecoin} balance in treasury`,
        metadata: { required: amountUsd, available: ethers.formatUnits(treasuryBalance, decimals) },
      });
      return;
    }
    
    // Send transaction
    console.log(`[Blockchain] Sending $${amountUsd} ${stablecoin} to ${toAddress}`);
    const tx = await contract.transfer(toAddress, amount);
    
    // Wait for confirmation
    const receipt = await tx.wait();
    
    // Update withdrawal record
    await supabase
      .from('withdrawals')
      .update({
        status: 'completed',
        tx_hash: receipt.hash,
        completed_at: new Date().toISOString(),
      })
      .eq('id', withdrawalId);
    
    console.log(`[Blockchain] Withdrawal ${withdrawalId} completed: ${receipt.hash}`);
    
    return receipt.hash;
    
  } catch (error) {
    console.error('[Blockchain] Transfer failed:', error);
    await markWithdrawalFailed(withdrawalId, error.message);
    throw error;
  }
}

/**
 * Mark withdrawal as failed and refund
 */
async function markWithdrawalFailed(withdrawalId, reason) {
  // Atomic status update: only proceed if status is still 'pending'
  // This prevents double-refunds if called multiple times
  const { data: updated, error: updateErr } = await supabase
    .from('withdrawals')
    .update({
      status: 'failed',
      failure_reason: reason,
    })
    .eq('id', withdrawalId)
    .eq('status', 'pending')  // Only update if still pending
    .select()
    .single();
  
  if (updateErr || !updated) {
    console.warn(`[Blockchain] Withdrawal ${withdrawalId} already processed or not found, skipping refund`);
    return;
  }
  
  // Refund user — use atomic increment to avoid read-then-write races
  const { data: balance } = await supabase
    .from('vae_balances')
    .select('available, total_withdrawn')
    .eq('user_id', updated.user_id)
    .single();
  
  if (!balance) {
    console.error(`[Blockchain] Cannot refund: no balance record for user ${updated.user_id}`);
    return;
  }
  
  const { error: refundErr } = await supabase
    .from('vae_balances')
    .update({
      available: balance.available + updated.amount_vae,
      total_withdrawn: Math.max(0, (balance.total_withdrawn || 0) - updated.amount_vae),
    })
    .eq('user_id', updated.user_id);
  
  if (refundErr) {
    console.error(`[Blockchain] CRITICAL: Refund update failed for withdrawal ${withdrawalId}:`, refundErr);
    return;
  }
  
  // Record refund transaction
  await supabase.from('transactions').insert({
    user_id: updated.user_id,
    type: 'withdrawal_refund',
    amount: updated.amount_vae,
    description: `Withdrawal refund: ${reason}`,
    metadata: { withdrawal_id: withdrawalId },
  });
  
  console.log(`[Blockchain] Withdrawal ${withdrawalId} failed and refunded ${updated.amount_vae} VAE: ${reason}`);
}

/**
 * Get withdrawal history for user
 */
async function getWithdrawalHistory(userId, limit = 10) {
  const { data } = await supabase
    .from('withdrawals')
    .select('*')
    .eq('user_id', userId)
    .order('created_at', { ascending: false })
    .limit(limit);
  
  return data || [];
}

/**
 * Retry failed withdrawals (called by scheduler)
 */
async function retryFailedWithdrawals() {
  // First: check if treasury has any stablecoins at all
  // If not, don't retry anything (avoids deduct/refund cycles that inflate balances)
  if (!provider || !wallet) {
    if (!initBlockchain()) {
      console.log('[Blockchain] Retry skipped: blockchain not configured');
      return;
    }
  }
  
  // Check treasury balance for at least one stablecoin
  let hasFunds = false;
  for (const [coin, addr] of Object.entries(STABLECOIN_ADDRESSES)) {
    try {
      const contract = new ethers.Contract(addr, ERC20_ABI, wallet);
      const bal = await contract.balanceOf(wallet.address);
      if (bal > 0n) {
        hasFunds = true;
        break;
      }
    } catch (e) {}
  }
  
  if (!hasFunds) {
    console.log('[Blockchain] Retry skipped: treasury has no stablecoin funds');
    return;
  }
  
  const { data: failed } = await supabase
    .from('withdrawals')
    .select('*')
    .eq('status', 'failed')
    .gt('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
  
  if (!failed?.length) return;
  
  console.log(`[Blockchain] Retrying ${failed.length} failed withdrawals`);
  
  for (const w of failed) {
    try {
      // Check user has enough balance
      const { data: bal } = await supabase
        .from('vae_balances')
        .select('available, total_withdrawn')
        .eq('user_id', w.user_id)
        .single();
      
      if (!bal || bal.available < w.amount_vae) {
        console.log(`[Blockchain] Skip retry ${w.id}: insufficient balance`);
        continue;
      }
      
      // Re-deduct the amount (with error checking)
      const { error: deductErr } = await supabase
        .from('vae_balances')
        .update({
          available: bal.available - w.amount_vae,
          total_withdrawn: (bal.total_withdrawn || 0) + w.amount_vae,
          updated_at: new Date().toISOString(),
        })
        .eq('user_id', w.user_id);
      
      if (deductErr) {
        console.error(`[Blockchain] Retry deduction failed for ${w.id}:`, deductErr);
        continue;
      }
      
      // Reset to pending (atomic - only if still failed)
      const { data: resetResult } = await supabase
        .from('withdrawals')
        .update({ status: 'pending', failure_reason: null })
        .eq('id', w.id)
        .eq('status', 'failed')
        .select()
        .single();
      
      if (!resetResult) {
        // Status already changed, undo deduction
        await supabase.from('vae_balances').update({
          available: bal.available,
          total_withdrawn: bal.total_withdrawn || 0,
        }).eq('user_id', w.user_id);
        console.warn(`[Blockchain] Retry ${w.id}: status already changed, deduction reversed`);
        continue;
      }
      
      // Retry transfer
      await processBlockchainTransfer(w.id, w.wallet_address, w.amount_usd, w.stablecoin);
    } catch (err) {
      console.error(`[Blockchain] Retry failed for ${w.id}:`, err.message);
    }
  }
}

module.exports = {
  initBlockchain,
  checkWithdrawalEligibility,
  getTreasuryBalance,
  processWithdrawal,
  getWithdrawalHistory,
  retryFailedWithdrawals,
  STABLECOIN_ADDRESSES,
  CONFIG,
};
