/**
 * Optimized Solana Trading Bot with Enhanced Profitability Focus
 * Version 3.5 - Complete Implementation
 */

const { Connection, Keypair, PublicKey, Transaction, ComputeBudgetProgram, TransactionInstruction } = require('@solana/web3.js');
const { TOKEN_PROGRAM_ID } = require('@solana/spl-token');
const fetch = require('node-fetch');
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
require('dotenv').config();

// Trading Configuration - OPTIMIZED FOR CURRENT MARKET CONDITIONS
const CONFIG = {
  // Connection Settings
  rpc: process.env.BACKUP_RPC_URL || "https://api.mainnet-beta.solana.com",
  wsUrl: process.env.BACKUP_WS_URL || "wss://api.mainnet-beta.solana.com",
  heliusRpc: process.env.PRIMARY_RPC_URL || "https://winny-rychu7-fast-mainnet.helius-rpc.com",
  heliusWs: process.env.PRIMARY_WS_URL || "wss://winny-rychu7-fast-mainnet.helius-rpc.com",
  apiKey: process.env.API_KEY || "86ab5634-df30-4a5b-bcfb-3f53d7095ca2",
  
  // Token addresses
  SOL_MINT: new PublicKey(process.env.SOL_MINT || "So11111111111111111111111111111111111111112"),
  
  // Programs to monitor
  MONITORED_PROGRAMS: [
    new PublicKey(process.env.RAYDIUM_PROGRAM_ID || "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), // Raydium
    new PublicKey(process.env.JUPITER_PROGRAM_ID || "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), // Jupiter
    new PublicKey("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"), // Meteora
  ],
  
  // CRITICAL TRADING PARAMETERS (MODIFIED FOR PROFITABILITY)
  tradeAmount: 0.023,       // Increased from 0.015 to improve profit opportunities
  slippage: 75,             // Increased from 40 to match .env setting (0.75%)
  priorityFee: 250000,      // Balanced (less than .env but more than original)
  maxTradesPerHour: parseInt(process.env.MAX_TRADES_PER_HOUR) || 30,
  reconnectDelay: 1000,     // WebSocket reconnect delay
  profitFile: "profit_report.csv",
  logsDir: "./mev_logs",
  tradingInterval: 15000,   // Increased from 10s to 15s to reduce frequency
  useSimulatedTrades: false,// REAL TRADES
  showOnlyProfits: false,   // Show all messages for debugging
  
  // Jupiter-specific configuration - OPTIMIZED
  jupiterMaxAccounts: 10,     // Increased from 8 to allow more complex routes
  jupiterRestrictTokens: false,// Allow intermediate tokens for better routes
  jupiterQuoteApi: "https://quote-api.jup.ag/v6/quote",
  jupiterInstructionsApi: "https://quote-api.jup.ag/v6/swap-instructions",
  
  // Execution timing - OPTIMIZED
  buyWaitTime: 5000,        // Wait time after buying
  sellWaitTime: 8000,       // Wait time after selling
  retryDelay: 1000,         // Time between retries
  
  // Critical for profit - ADJUSTED FOR CURRENT MARKET
  profitabilityCheckEnabled: true,
  minimumProfitPercent: 0.1,  // Reduced from 0.2% to 0.1%
  minProfitAmount: 0.00008,   // Reduced from 0.0001 to 0.00008 SOL
  
  // Transaction settings
  skipPreflight: true,
  maxRetries: 3,
  maxTransactionSize: parseInt(process.env.MAX_TRANSACTION_SIZE) || 1232,
  sellAdjustmentFactor: 0.97, // Adjusted sell amount factor
};

// Token Strategy Map - OPTIMIZED FOR CURRENT MARKET CONDITIONS
const PROFIT_TOKENS = {
  "BONK": {
    address: new PublicKey(process.env.BONK_ADDRESS || "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"),
    minProfit: 0.00008,     // Reduced minimum profit requirement
    weight: 60,             // Increased weight to match .env setting
    exchangePriority: ["Jupiter", "Raydium"],
  },
  "WIF": {
    address: new PublicKey(process.env.WIF_ADDRESS || "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm"),
    minProfit: 0.00009,     // Reduced from 0.00014 to match current market
    weight: 30,             // Matching .env setting
    exchangePriority: ["Jupiter"],
  },
  "POPCAT": {
    address: new PublicKey("HgMaFiUAT5V9HWBgpqBVcvJLWDYyJxCYdkjnHY26QiXH"),
    minProfit: 0.0001,      // Reduced from 0.00018
    weight: 20,             // Kept the same
    exchangePriority: ["Jupiter", "Raydium"],
  },
  "SAMO": {
    address: new PublicKey(process.env.SAMO_ADDRESS || "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"),
    minProfit: 0.00008,     // New token with lower threshold
    weight: 10,             // Match .env setting
    exchangePriority: ["Jupiter", "Raydium"],
  }
};

// Trading Bot Class
class SolanaTradingBot {
  constructor() {
    this.keypair = null;
    this.connection = null;
    this.wsConnection = null;
    this.isTrading = false;
    this.heartbeatInterval = null;
    this.tradingInterval = null;
    this.tokenPriceMap = {};
    this.tradeHistory = [];
    this.pendingTransactions = new Set();
    this.lastTradeTime = 0;
    this.totalProfit = 0;
    this.tradeCount = 0;
    this.successRate = 0;
    this.hourlyProfits = 0;
    this.dailyProfits = 0;
    this.lastHourReset = Date.now();
    this.lastDayReset = Date.now();
    this.lastQuoteOutAmount = 0;    // Store the last quote outAmount
    this.lastBuyAmount = 0;         // Track the last buy amount
    this.tokenPerformance = {};     // Track token performance
    this.marketConditions = {};     // Track market conditions
    this.potentialTradeQueue = [];  // Queue of potential trading opportunities
    this.rpcEndpoints = [
      CONFIG.heliusRpc,  // Try Helius first
      CONFIG.rpc,
      "https://api.mainnet-beta.solana.com",
    ];
    this.currentRpcIndex = 0;
    this.currentTokenIndex = 0;
    this.lastFailedToken = null;    // Track last unsuccessful token
    this.failedAttempts = {};       // Track failed attempts by token
    
    // Create necessary directories
    this.setupDirectories();
    
    // Set token prices
    this.initializeDefaultPrices();
    
    // Initialize token performance tracking
    this.initializeTokenPerformance();
  }
  
  setupDirectories() {
    if (!fs.existsSync(CONFIG.logsDir)) {
      fs.mkdirSync(CONFIG.logsDir, { recursive: true });
    }
    
    // Initialize profit log with headers if it doesn't exist
    if (!fs.existsSync(CONFIG.profitFile)) {
      fs.writeFileSync(CONFIG.profitFile, "timestamp,token,input_amount,output_amount,profit_sol,profit_usd,execution_time_ms,exchange\n");
    }
  }
  
  // Initialize token performance tracking
  initializeTokenPerformance() {
    for (const [symbol, data] of Object.entries(PROFIT_TOKENS)) {
      this.tokenPerformance[symbol] = {
        trades: 0,
        totalProfit: 0,
        averageProfit: 0,
        successRate: 0,
        successfulTrades: 0,
        lastTradeTime: 0,
        failCount: 0
      };
      
      // Initialize market conditions
      this.marketConditions[symbol] = {
        lastPrice: 0,
        priceHistory: [],
        volatility: 0,
        trend: 0, // -1 downtrend, 0 neutral, 1 uptrend
        lastUpdate: 0
      };
      
      this.failedAttempts[symbol] = 0;
    }
  }
  
  // Get the best token to trade based on performance, market conditions and rotation
  getBestToken() {
    const tokenSymbols = Object.keys(PROFIT_TOKENS);
    const now = Date.now();
    
    // Skip tokens that have had too many consecutive failed attempts
    const eligibleTokens = tokenSymbols.filter(symbol => 
      this.failedAttempts[symbol] < 5
    );
    
    if (eligibleTokens.length === 0) {
      // Reset all failed attempts if all tokens have too many failures
      for (const symbol of tokenSymbols) {
        this.failedAttempts[symbol] = 0;
      }
      this.log("WARN", "Reset failed attempts counter for all tokens");
    }
    
    // Prioritize tokens that haven't been traded recently
    const tokensByLastTrade = [...tokenSymbols].sort((a, b) => {
      const lastTradeA = this.tokenPerformance[a].lastTradeTime || 0;
      const lastTradeB = this.tokenPerformance[b].lastTradeTime || 0;
      return lastTradeA - lastTradeB;
    });
    
    // If we have performance data and have made at least a few trades
    if (Object.values(this.tokenPerformance).some(p => p.trades > 5)) {
      // Find the most profitable token considering recency
      let bestToken = null;
      let bestScore = -Infinity;
      
      for (const symbol of eligibleTokens) {
        const performance = this.tokenPerformance[symbol];
        if (performance.trades === 0) continue;
        
        // Score based on success rate and average profit
        // Add recency factor - tokens not traded recently get a boost
        const recencyFactor = Math.min(1, (now - performance.lastTradeTime) / 300000); // 5 minute max boost
        
        const score = (performance.successRate * 0.6) + 
                      (performance.averageProfit * 300000 * 0.25) + 
                      (recencyFactor * 15); // Recency boost
                      
        if (score > bestScore) {
          bestScore = score;
          bestToken = symbol;
        }
      }
      
      if (bestToken) {
        this.log("DEBUG", `Selected ${bestToken} based on performance score: ${bestScore.toFixed(2)}`);
        return bestToken;
      }
    }
    
    // If no token selected by performance, try the least recently traded eligible token
    for (const symbol of tokensByLastTrade) {
      if (eligibleTokens.includes(symbol)) {
        this.log("DEBUG", `Selected ${symbol} based on trade rotation (least recently traded)`);
        return symbol;
      }
    }
    
    // Fallback to weighted random selection if no other method works
    // Use the token weights to determine probability
    const tokenWeights = eligibleTokens.map(symbol => PROFIT_TOKENS[symbol].weight);
    const totalWeight = tokenWeights.reduce((sum, weight) => sum + weight, 0);
    
    // Get a random number between 0 and totalWeight
    const randomValue = Math.random() * totalWeight;
    
    // Find which token this corresponds to
    let cumulativeWeight = 0;
    for (let i = 0; i < eligibleTokens.length; i++) {
      cumulativeWeight += tokenWeights[i];
      if (randomValue <= cumulativeWeight) {
        this.log("DEBUG", `Selected ${eligibleTokens[i]} based on weighted random selection`);
        return eligibleTokens[i];
      }
    }
    
    // Default to BONK if somehow nothing was selected
    return "BONK";
  }
  
  // Rotate RPC endpoints if one fails
  switchRpcEndpoint() {
    this.currentRpcIndex = (this.currentRpcIndex + 1) % this.rpcEndpoints.length;
    const newRpc = this.rpcEndpoints[this.currentRpcIndex];
    this.log("INFO", `Switching to RPC endpoint: ${newRpc}`);
    return newRpc;
  }
  
  // Enhanced colored logging with timestamps
  log(type, message) {
    // Skip non-profit messages if showOnlyProfits is true
    if (CONFIG.showOnlyProfits && type !== "PROFIT" && type !== "ERROR" && type !== "SUCCESS") {
      return;
    }
    
    const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
    const colors = {
      "DEBUG": "\x1b[90m", // Gray
      "INFO": "\x1b[36m",  // Cyan
      "WARN": "\x1b[33m",  // Yellow
      "ERROR": "\x1b[31m", // Red
      "TRADE": "\x1b[35m", // Magenta
      "SUCCESS": "\x1b[32m", // Green
      "PROFIT": "\x1b[42m\x1b[30m" // Green background with black text
    };
    
    const colorCode = colors[type] || "\x1b[0m";
    console.log(`${colorCode}[${type}]\x1b[0m ${timestamp} ${message}`);
    
    // Also log to file for important events
    if (type === "TRADE" || type === "ERROR" || type === "PROFIT" || type === "SUCCESS") {
      const logFile = path.join(CONFIG.logsDir, `${new Date().toISOString().split('T')[0]}_mev.log`);
      fs.appendFileSync(logFile, `[${type}] ${timestamp} ${message}\n`);
    }
  }
  
  // Initialize default token prices
  initializeDefaultPrices() {
    // Set baseline prices
    this.tokenPriceMap = {
      "SOL": { price: 115.50, lastUpdate: Date.now() },
      "BONK": { price: 0.000012, lastUpdate: Date.now() },
      "WIF": { price: 0.17, lastUpdate: Date.now() },
      "POPCAT": { price: 0.004, lastUpdate: Date.now() },
      "SAMO": { price: 0.02, lastUpdate: Date.now() },
    };
    
    this.log("INFO", "Set baseline token prices for trading decisions");
  }
  
  // Initialize the bot
  async initialize() {
    try {
      this.log("INFO", "Initializing Solana Trading Bot...");
      
      // Load wallet
      await this.loadKeypair();
      this.log("INFO", `Wallet initialized: ${this.keypair.publicKey.toString()}`);
      
      // Create connection with robust fallback
      let rpcUrl = this.rpcEndpoints[this.currentRpcIndex];
      let connectionSuccess = false;
      
      for (let attempt = 0; attempt < this.rpcEndpoints.length && !connectionSuccess; attempt++) {
        try {
          this.connection = new Connection(rpcUrl, {
            commitment: 'confirmed',
            confirmTransactionInitialTimeout: 30000
          });
          
          // Test connection
          await this.connection.getBlockHeight();
          connectionSuccess = true;
          this.log("SUCCESS", `Connected to RPC: ${rpcUrl}`);
        } catch (err) {
          this.log("WARN", `RPC connection failed: ${err.message}. Trying next endpoint.`);
          rpcUrl = this.switchRpcEndpoint();
        }
      }
      
      if (!connectionSuccess) {
        throw new Error("Failed to connect to any RPC endpoints");
      }
      
      // Check wallet balance
      const balance = await this.connection.getBalance(this.keypair.publicKey);
      const solBalance = balance / 1e9;
      
      this.log("INFO", `SOL Balance: ${solBalance} SOL`);
      
      // Load profit history from file
      this.loadProfitHistory();
      
      this.log("SUCCESS", "Solana Trading Bot initialized and ready!");
      return true;
    } catch (err) {
      this.log("ERROR", `Initialization failed: ${err.message}`);
      return false;
    }
  }
  
  // Load keypair from multiple possible locations
  async loadKeypair() {
    const keypairPaths = [
      path.join(process.cwd(), 'keypair.bin'),
      path.join(process.cwd(), 'id.json'),
      path.resolve('..', 'id.json'),
      path.join('C:', 'solana_rust_bot', 'id.json'),
      path.join('C:', 'solana_rust_bot', 'keypair.bin')
    ];
    
    for (const kpPath of keypairPaths) {
      if (fs.existsSync(kpPath)) {
        this.log("INFO", `Found keypair at: ${kpPath}`);
        
        try {
          if (kpPath.endsWith('.bin')) {
            const keypairData = fs.readFileSync(kpPath);
            this.keypair = Keypair.fromSecretKey(new Uint8Array(keypairData));
            return;
          } 
          else if (kpPath.endsWith('.json')) {
            const keypairData = JSON.parse(fs.readFileSync(kpPath, 'utf-8'));
            this.keypair = Keypair.fromSecretKey(new Uint8Array(keypairData));
            return;
          }
        } catch (e) {
          this.log("WARN", `Failed to load keypair from ${kpPath}: ${e.message}`);
        }
      }
    }
    
    throw new Error("No wallet keypair found. Please check keypair paths.");
  }
  
  // Load profit history from file
  loadProfitHistory() {
    try {
      if (fs.existsSync(CONFIG.profitFile)) {
        const data = fs.readFileSync(CONFIG.profitFile, 'utf-8');
        const lines = data.split('\n').filter(line => line.trim() !== '');
        
        // Skip header
        if (lines.length > 1) {
          // Calculate totals
          let totalProfit = 0;
          let tradeCount = 0;
          let successCount = 0;
          
          for (let i = 1; i < lines.length; i++) {
            const parts = lines[i].split(',');
            if (parts.length >= 5) {
              const profit = parseFloat(parts[4]);
              const token = parts[1];
              totalProfit += profit;
              tradeCount++;
              if (profit > 0) successCount++;
              
              // Update token performance
              if (token && this.tokenPerformance[token]) {
                this.tokenPerformance[token].trades++;
                this.tokenPerformance[token].totalProfit += profit;
                if (profit > 0) {
                  this.tokenPerformance[token].successfulTrades++;
                }
                
                // Calculate averages
                this.tokenPerformance[token].averageProfit = 
                  this.tokenPerformance[token].totalProfit / this.tokenPerformance[token].trades;
                this.tokenPerformance[token].successRate = 
                  (this.tokenPerformance[token].successfulTrades / this.tokenPerformance[token].trades) * 100;
              }
            }
          }
          
          this.totalProfit = totalProfit;
          this.tradeCount = tradeCount;
          this.successRate = tradeCount > 0 ? (successCount / tradeCount) * 100 : 0;
          
          this.log("INFO", `Loaded profit history: ${this.totalProfit.toFixed(6)} SOL profit from ${this.tradeCount} trades (${this.successRate.toFixed(2)}% success rate)`);
          
          // Log token performance
          for (const [symbol, perf] of Object.entries(this.tokenPerformance)) {
            if (perf.trades > 0) {
              this.log("INFO", `${symbol}: ${perf.trades} trades, avg profit: ${perf.averageProfit.toFixed(8)} SOL, success: ${perf.successRate.toFixed(2)}%`);
            }
          }
        }
      }
    } catch (error) {
      this.log("WARN", `Error loading profit history: ${error.message}`);
    }
  }
  
  // Connect to WebSocket for market monitoring
  async connectWebSocket() {
    try {
      this.log("INFO", "Connecting to WebSocket for market monitoring...");
      
      // Try Helius WebSocket first, then fallback to default
      let wsUrl = CONFIG.heliusWs;
      let headers = { 'api-key': CONFIG.apiKey };
      
      const ws = new WebSocket(wsUrl, { headers });
      
      // Connection timeout
      const connectTimeout = setTimeout(() => {
        this.log("ERROR", "WebSocket connection timeout");
        if (ws.readyState === WebSocket.OPEN) {
          ws.terminate();
        }
      }, 15000);
      
      // WebSocket event handlers
      ws.on('open', () => {
        this.log("SUCCESS", "WebSocket connected");
        clearTimeout(connectTimeout);
        
        // Setup heartbeat ping
        this.heartbeatInterval = setInterval(() => {
          if (ws && ws.readyState === WebSocket.OPEN) {
            try {
              ws.ping();
            } catch (e) {
              this.log("WARN", `Heartbeat error: ${e.message}`);
              this.cleanupWebSocket(ws);
              this.scheduleReconnect();
            }
          }
        }, 30000);
        
        // Subscribe to all monitored programs
        CONFIG.MONITORED_PROGRAMS.forEach((programId, index) => {
          const programSub = {
            jsonrpc: "2.0",
            id: index + 1,
            method: "logsSubscribe",
            params: [
              { mentions: [programId.toString()] },
              { commitment: 'confirmed' }
            ]
          };
          
          ws.send(JSON.stringify(programSub));
          this.log("DEBUG", `Subscribed to program: ${programId.toString()}`);
        });
        
        // Also subscribe to our wallet account
        const accountSub = {
          jsonrpc: "2.0",
          id: CONFIG.MONITORED_PROGRAMS.length + 1,
          method: "accountSubscribe",
          params: [
            this.keypair.publicKey.toString(),
            { commitment: 'confirmed' }
          ]
        };
        
        ws.send(JSON.stringify(accountSub));
      });
      
      // Handle incoming WebSocket messages
      ws.on('message', (data) => {
        try {
          const message = JSON.parse(data);
          
          // Handle subscription confirmations
          if (message.id !== undefined && message.result !== undefined) {
            this.log("DEBUG", `Subscription ${message.id} confirmed`);
            return;
          }
          
          // Process transaction logs
          if (message.params?.result?.value?.logs) {
            const logs = message.params.result.value.logs;
            const signature = message.params.result.value.signature;
            
            // Analyze for MEV opportunities
            this.analyzeMEVOpportunity(logs, signature);
          }
          
          // Handle wallet balance updates
          if (message.params?.result?.value?.lamports) {
            const lamports = message.params.result.value.lamports;
            this.log("DEBUG", `Balance update: ${lamports / 1e9} SOL`);
          }
        } catch (error) {
          this.log("ERROR", `WebSocket message processing error: ${error.message}`);
        }
      });
      
      // Error handling
      ws.on('error', (error) => {
        this.log("ERROR", `WebSocket error: ${error.message}`);
        clearTimeout(connectTimeout);
        this.cleanupWebSocket(ws);
        this.scheduleReconnect();
      });
      
      // Handle disconnection
      ws.on('close', (code, reason) => {
        this.log("INFO", `WebSocket closed: ${code} - ${reason}`);
        clearTimeout(connectTimeout);
        this.cleanupWebSocket(ws);
        this.scheduleReconnect();
      });
      
      this.wsConnection = ws;
      return ws;
    } catch (err) {
      this.log("ERROR", `WebSocket connection error: ${err.message}`);
      this.scheduleReconnect();
      return null;
    }
  }
  
  // Start high-frequency trading interval
  startTradingInterval() {
    if (this.tradingInterval) {
      clearInterval(this.tradingInterval);
    }
    
    this.tradingInterval = setInterval(() => {
      // Only trade if not already in a trade and enough time has passed
      const timeSinceLastTrade = Date.now() - this.lastTradeTime;
      
      if (!this.isTrading && timeSinceLastTrade > CONFIG.tradingInterval) {
        // Check if we have any queued opportunities
        if (this.potentialTradeQueue.length > 0) {
          // Process the oldest opportunity first
          const opportunity = this.potentialTradeQueue.shift();
          this.log("INFO", `Processing queued trade opportunity for ${opportunity.token}...`);
          this.executeRealTrade(opportunity.token);
        } else {
          // Otherwise get the best token based on strategy
          const tokenToTrade = this.getBestToken();
          this.executeRealTrade(tokenToTrade);
        }
      }
      
      // Update profit metrics every minute
      const now = Date.now();
      
      // Reset hourly metrics if an hour has passed
      if (now - this.lastHourReset > 3600000) {
        this.log("INFO", `Hourly profit: ${this.hourlyProfits.toFixed(6)} SOL`);
        this.hourlyProfits = 0;
        this.lastHourReset = now;
      }
      
      // Reset daily metrics if a day has passed
      if (now - this.lastDayReset > 86400000) {
        this.log("INFO", `Daily profit: ${this.dailyProfits.toFixed(6)} SOL`);
        this.dailyProfits = 0;
        this.lastDayReset = now;
      }
    }, Math.floor(CONFIG.tradingInterval / 2)); // Run check more frequently than actual trade interval
    
    this.log("SUCCESS", `Trading started: executing every ${CONFIG.tradingInterval/1000} seconds (Real mode: ${!CONFIG.useSimulatedTrades})`);
  }
  
  // Clean up WebSocket resources
  cleanupWebSocket(ws) {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
    
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.close();
    }
    
    this.wsConnection = null;
  }
  
  // Schedule WebSocket reconnection
  scheduleReconnect(delay = CONFIG.reconnectDelay) {
    this.log("INFO", `Reconnecting in ${delay/1000} seconds...`);
    
    setTimeout(() => {
      this.connectWebSocket();
    }, delay);
  }
  
  // Get current USD price for SOL
  getSOLPrice() {
    if (this.tokenPriceMap['SOL'] && this.tokenPriceMap['SOL'].price) {
      return this.tokenPriceMap['SOL'].price;
    }
    // Fallback price
    return 115.50;
  }
  
  // Get Jupiter quote with retry
  async getJupiterQuote(inputMint, outputMint, amount, retry = 0) {
    try {
      // Add parameters to control transaction size
      const quoteUrl = `${CONFIG.jupiterQuoteApi}?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${CONFIG.slippage}&restrictIntermediateTokens=${CONFIG.jupiterRestrictTokens}&maxAccounts=${CONFIG.jupiterMaxAccounts}`;
      
      const quoteResponse = await fetch(quoteUrl);
      if (!quoteResponse.ok) {
        throw new Error(`Quote API error: ${quoteResponse.status}`);
      }
      
      const quoteData = await quoteResponse.json();
      
      if (!quoteData || !quoteData.outAmount) {
        throw new Error(`Invalid quote data`);
      }
      
      return quoteData;
    } catch (error) {
      if (retry < 2) {
        this.log("WARN", `Jupiter quote error, retrying (${retry+1}/2): ${error.message}`);
        await new Promise(resolve => setTimeout(resolve, 1000));
        return this.getJupiterQuote(inputMint, outputMint, amount, retry + 1);
      }
      this.log("ERROR", `Jupiter quote error: ${error.message}`);
      return null;
    }
  }
  
  // Check if a market opportunity meets our profitability criteria
  async isProfitableOpportunity(tokenSymbol, checkOnly = false) {
    const token = PROFIT_TOKENS[tokenSymbol];
    if (!token) return false;
    
    // Use configured trade amount
    const tradeAmount = CONFIG.tradeAmount;
    const lamportsAmount = Math.floor(tradeAmount * 1e9); // Convert to lamports
    
    try {
      // Get buy quote
      const buyQuote = await this.getJupiterQuote(
        CONFIG.SOL_MINT.toString(),
        token.address.toString(),
        lamportsAmount
      );
      
      if (!buyQuote) {
        if (!checkOnly) {
          this.log("ERROR", `Failed to get buy quote for ${tokenSymbol}`);
        }
        return false;
      }
      
      // Store expected tokens we'll receive
      const expectedTokenAmount = buyQuote.outAmount;
      
      // Get sell quote to check if this will be profitable
      const sellQuote = await this.getJupiterQuote(
        token.address.toString(), 
        CONFIG.SOL_MINT.toString(),
        Math.floor(expectedTokenAmount * CONFIG.sellAdjustmentFactor) // Apply sell adjustment factor
      );
      
      if (!sellQuote) {
        if (!checkOnly) {
          this.log("ERROR", `Failed to get sell quote for ${tokenSymbol}`);
        }
        return false;
      }
      
      // Calculate expected SOL spent for buying
      const inAmount = Number(buyQuote.inAmount) / 1e9; // Convert lamports to SOL
      
      // Calculate expected SOL received after selling
      const outAmountLamports = Number(sellQuote.outAmount);
      const outAmount = outAmountLamports / 1e9; // Convert lamports to SOL
      
      // Calculate expected profit
      const profit = outAmount - inAmount;
      const profitPercentage = (profit / inAmount) * 100;
      
      // Get token's minimum acceptable profit
      const minProfit = token.minProfit || CONFIG.minProfitAmount;
      const minProfitPercent = CONFIG.minimumProfitPercent;
      
      if (!checkOnly) {
        this.log("DEBUG", `Opportunity check - Token: ${tokenSymbol}, Expected profit: ${profit.toFixed(8)} SOL (${profitPercentage.toFixed(2)}%), Min: ${minProfit} SOL (${minProfitPercent}%)`);
      }
      
      // Dynamic profit threshold based on market conditions
      const marketCondition = this.marketConditions[tokenSymbol];
      let dynamicThreshold = minProfitPercent;
      
      // If market is trending up, we can accept slightly lower profits
      if (marketCondition.trend > 0) {
        dynamicThreshold = minProfitPercent * 0.9;
      }
      
      // Return true if both absolute profit and percentage profit meet minimum thresholds
      const isProfitable = profit >= minProfit && profitPercentage >= dynamicThreshold;
      
      // Update market condition data
      if (!checkOnly) {
        this.updateMarketCondition(tokenSymbol, profit, isProfitable);
      }
      
      return isProfitable;
    } catch (error) {
      if (!checkOnly) {
        this.log("ERROR", `Opportunity check error for ${tokenSymbol}: ${error.message}`);
      }
      return false;
    }
  } i need this script written with the missing code from the bottom please