# Change these method calls:
sol_balance = await self.trading_core.get_sol_balance()  # Remove _py suffix
wsol_balance = await self.trading_core.get_wsol_balance()  # Remove _py suffix   import asyncio
import json
import base64
import base58
from datetime import datetime
import websockets
import torch
import os
from typing import Optional, Dict
from dataclasses import dataclass
from pathlib import Path
import struct

from solana_logger import logger
from config_loader import config
from solana_rust_bot import TradingCore, TradeParams

@dataclass
class TradeOpportunity:
    victim_data: bytes
    amount: float
    token_a_mint: str
    token_b_mint: str
    net_profit: float
    instruction_type: int
    transaction_id: str

class MEVBot:
    def __init__(self):
        # Load configuration
        self.load_config()
        
        # Initialize core components
        self.init_components()
        
        # Trading parameters
        trade_config = config.get_trade_config()
        self.POSITION_SIZE = trade_config['front_run_amount']
        self.MIN_VICTIM_SIZE = trade_config['min_victim_size']
        self.MAX_VICTIM_SIZE = trade_config['max_victim_size']
        self.MIN_PROFIT = trade_config['min_profit']
        self.LAMPORTS_PER_SOL = 1_000_000_000
        
        # Track processed transactions
        self.processed_txs = set()
        
        # Balance monitoring
        balance_config = config.get_balance_config()
        self.min_sol_balance = balance_config['min_sol_balance']
        self.min_wsol_balance = balance_config['min_wsol_balance']
        self.balances = {"SOL": 0.0, "WSOL": 0.0}
        self.is_trading_paused = False
        
        # Performance tracking
        self.active = True
        self.trades_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = datetime.now()
        
        # Trade control
        self.trade_lock = asyncio.Lock()
        self.active_trades = 0
        
        # Setup CUDA
        self._setup_cuda()
        
        self.log_startup()

    def load_config(self):
        """Load configuration and network settings"""
        network_config = config.get_network_config()
        self.ws_url = network_config['ws_url']
        self.api_key = network_config['api_key']
        self.program_id = config.get('raydium')
        
        # Account configuration
        self.wallet = config.get('wallet')
        self.wsol_account = config.get('wsol_token_account')

    def init_components(self):
        """Initialize core components"""
        try:
            self.trading_core = TradingCore(config.get_network_config()['rpc_url'])
            logger.system("Core components initialized successfully")
        except Exception as e:
            raise RuntimeError(f"Failed to initialize components: {e}")

    def _setup_cuda(self):
        """Configure CUDA for optimal performance"""
        if not torch.cuda.is_available():
            raise RuntimeError("CUDA required!")
        torch.cuda.empty_cache()
        torch.set_float32_matmul_precision('high')
        torch.backends.cudnn.benchmark = True
        torch.backends.cudnn.enabled = True
        self.cuda_stream = torch.cuda.Stream(priority=-1)
        logger.system(f"Using GPU: {torch.cuda.get_device_name(0)}")

    def log_startup(self):
        """Log startup information"""
        logger.system(f"""
🚀 MEV Bot Starting
====================
Position Size: {self.POSITION_SIZE} SOL
Trade Range: {self.MIN_VICTIM_SIZE}-{self.MAX_VICTIM_SIZE} SOL
Min Profit: {self.MIN_PROFIT} SOL
====================
Wallet: {self.wallet}
WSOL Account: {self.wsol_account}
====================
""")

    async def check_initial_balances(self):
        """Check initial balances via RPC"""
        try:
            sol_balance = await self.trading_core.get_sol_balance_py()
            wsol_balance = await self.trading_core.get_wsol_balance_py()
            
            self.balances["SOL"] = sol_balance
            self.balances["WSOL"] = wsol_balance
            
            logger.info(f"""
Initial Balance Check:
SOL: {sol_balance:.4f}
WSOL: {wsol_balance:.4f}
""")

            if sol_balance < self.min_sol_balance:
                logger.warning(f"Low SOL balance detected: {sol_balance:.4f}")
                await self._handle_rebalance()
                
        except Exception as e:
            logger.error(f"Balance check error: {e}")

    def calculate_profit(self, amount: float) -> float:
        """Calculate potential trade profit"""
        with torch.cuda.stream(self.cuda_stream):
            amount_tensor = torch.tensor(amount, device='cuda')
            
            if amount >= 40:
                profit_rate = 0.025
            elif amount >= 20:
                profit_rate = 0.020
            elif amount >= 5:
                profit_rate = 0.015
            else:
                return 0.0
            
            entry_impact = 0.001 * amount_tensor
            exit_impact = entry_impact
            priority_fee = 0.002
            
            net_profit = (amount_tensor * profit_rate) - (entry_impact + exit_impact + priority_fee)
            return float(net_profit) if net_profit > self.MIN_PROFIT else 0.0

    def decode_trade_data(self, raw_data: bytes, tx_id: str) -> Optional[TradeOpportunity]:
        """Decode and validate trade data"""
        try:
            if tx_id in self.processed_txs:
                return None
                
            if len(raw_data) < 40:
                return None

            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], "little") / 1e9
            
            if instruction != 0x03 or not (self.MIN_VICTIM_SIZE <= amount <= self.MAX_VICTIM_SIZE):
                return None
            
            token_a_bytes = raw_data[40:72] if len(raw_data) >= 72 else None
            token_b_bytes = raw_data[72:104] if len(raw_data) >= 104 else None
            
            if not token_a_bytes or not token_b_bytes:
                return None
                
            token_a_mint = base58.b58encode(token_a_bytes).decode()
            token_b_mint = base58.b58encode(token_b_bytes).decode()
            
            profit = self.calculate_profit(amount)
            if profit <= 0:
                return None
                
            self.processed_txs.add(tx_id)
            
            return TradeOpportunity(
                victim_data=raw_data,
                amount=amount,
                token_a_mint=token_a_mint,
                token_b_mint=token_b_mint,
                net_profit=profit,
                instruction_type=instruction,
                transaction_id=tx_id
            )
                
        except Exception as e:
            logger.error(f"Trade data decode error: {e}")
            return None

    async def execute_trade(self, trade: TradeOpportunity) -> bool:
        """Execute trade with guaranteed execution or no entry"""
        if self.is_trading_paused or self.active_trades > 0:
            return False

        async with self.trade_lock:
            try:
                self.active_trades = 1
                position_lamports = int(self.POSITION_SIZE * self.LAMPORTS_PER_SOL)

                # Get market accounts
                market_accounts = [
                    config.get('raydium'),                     # Program ID
                    config.get_market_accounts()['amm_authority'],  
                    config.get_market_accounts()['amm_open_orders'],
                    config.get_market_accounts()['amm_target'], 
                    config.get_market_accounts()['market'],    
                    config.get_market_accounts()['base_vault'],
                    config.get_market_accounts()['quote_vault'],
                    config.get_market_accounts()['serum_market'],
                    config.get_market_accounts()['serum_bids'],
                    config.get_market_accounts()['serum_asks'],
                    config.get_market_accounts()['event_queue'],
                    config.get_market_accounts()['vault_signer'],
                    self.wsol_account,
                    config.get('token'),
                    self.wallet,
                ]

                # Try frontrun entry
                entry_params = TradeParams(
                    accounts=market_accounts,
                    amount_in=position_lamports,
                    is_buy=True,
                    token_a_mint=trade.token_a_mint,
                    token_b_mint=trade.token_b_mint,
                    victim_data=trade.victim_data,
                    sell_all=False
                )

                entry_result = await self.trading_core.execute_trade_py(entry_params)
                if not entry_result:
                    logger.info("Failed to frontrun, skipping trade")
                    return False

                logger.trade(f"""
🎯 Successful Entry:
Size: {trade.amount:.4f} SOL
Position: {self.POSITION_SIZE} SOL ({position_lamports} lamports)
Token A: {trade.token_a_mint}
WSOL: {self.wsol_account}
Entry TX: {entry_result}
Original TX: {trade.transaction_id}
""")

                await asyncio.sleep(0.2)

                # Try exit with retries
                for attempt in range(4):  # Up to 4 exit attempts
                    try:
                        exit_params = TradeParams(
                            accounts=market_accounts,
                            amount_in=None,
                            is_buy=False,
                            token_a_mint=trade.token_a_mint,
                            token_b_mint=trade.token_b_mint,
                            victim_data=None,
                            sell_all=True
                        )
                    
                        exit_result = await self.trading_core.execute_trade_py(exit_params)
                        if exit_result:
                            self.trades_executed += 1
                            self.successful_trades += 1
                            self.total_profit += trade.net_profit

                            logger.trade(f"""
💰 Trade Complete:
Entry TX: {entry_result}
Exit TX: {exit_result}
Attempts: {attempt + 1}
Profit: {trade.net_profit:.6f} SOL
Total Profit: {self.total_profit:.6f} SOL
Success Rate: {(self.successful_trades/self.trades_executed*100):.1f}%
""")
                            return True
                            
                        if attempt < 3:  # Don't sleep after last attempt
                            await asyncio.sleep(0.2)
                            
                    except Exception as e:
                        logger.error(f"Exit attempt {attempt + 1} failed: {e}")
                        if attempt < 3:
                            await asyncio.sleep(0.2)

                logger.error("Failed to exit after all attempts")
                return False

            except Exception as e:
                logger.error(f"Trade execution error: {e}")
                return False
            finally:
                self.active_trades = 0
                await asyncio.sleep(0.2)

    async def monitor_balances(self):
        """Monitor balances using WebSocket"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={'api-key': self.api_key}
                ) as ws:
                    accounts = {
                        "SOL": self.wallet,
                        "WSOL": self.wsol_account
                    }
                    
                    for acc_type, address in accounts.items():
                        await ws.send(json.dumps({
                            "jsonrpc": "2.0",
                            "id": 1 if acc_type == "SOL" else 2,
                            "method": "accountSubscribe",
                            "params": [
                                address,
                                {"encoding": "base64", "commitment": "confirmed"}
                            ]
                        }))

                    subs = {}
                    while self.active:
                        msg = await ws.recv()
                        data = json.loads(msg)
                        
                        if "result" in data:
                            if data["id"] in [1, 2]:
                                subs[data["result"]] = "SOL" if data["id"] == 1 else "WSOL"
                            continue

                        if "method" in data and data["method"] == "accountNotification":
                            params = data["params"]
                            sub_id = params["subscription"]
                            
                            if sub_id in subs:
                                acc_type = subs[sub_id]
                                lamports = params["result"]["value"]["lamports"]
                                balance = lamports / 1e9
                                
                                old_balance = self.balances.get(acc_type, 0)
                                self.balances[acc_type] = balance
                                
                                if abs(balance - old_balance) > 0.0001:
                                    logger.info(f"{acc_type} Balance: {balance:.4f} ({'+' if balance >= old_balance else ''}{balance - old_balance:.4f})")
                                    
                                if acc_type == "SOL" and balance < self.min_sol_balance:
                                    await self._handle_rebalance()
                            
            except websockets.ConnectionClosed:
                logger.warning("WebSocket disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Monitor error: {e}")
                await asyncio.sleep(1)

    async def process_mempool(self):
        """Process Raydium mempool data"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={
                        'api-key': self.api_key,
                        'User-Agent': 'solana-client/1.18.23'
                    },
                    ping_interval=5,
                    ping_timeout=10,
                    compression=None
                ) as ws:
                    logger.info("Mempool WebSocket Connected")
                    
                    await ws.send(json.dumps({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [self.program_id]},
                            {"commitment": "processed"}
                        ]
                    }))
                    
                    await ws.recv()  # Subscription confirmation
                    
                    while self.active:
                        try:
                            msg = await ws.recv()
                            data = json.loads(msg)
                            
                            if "params" in data:
                                result = data["params"].get("result", {})
                                logs = result.get("value", {}).get("logs", [])
                                tx_id = result.get("value", {}).get("signature", "unknown")
                                
                                for log in logs:
                                    if "ray_log:" in log:
                                        raw_data = base64.b64decode(log.split("ray_log:")[1].strip())
                                        
                                        trade = self.decode_trade_data(raw_data, tx_id)
                                        if trade:
                                            self.trades_found += 1
                                            await self.execute_trade(trade)
                                    
                        except Exception as e:
                            logger.info(f"Message processing error: {e}")
                            continue
                            
            except websockets.ConnectionClosed:
                logger.warning("Mempool WebSocket disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Mempool connection error: {e}")
                await asyncio.sleep(1)

    async def _handle_rebalance(self):
        """Handle low balance conditions"""
        if self.is_trading_paused:
            return
            
        try:
            self.is_trading_paused = True
            logger.info("Starting balance rebalance")
            
            # Check balances via RPC
            sol_balance = await self.trading_core.get_sol_balance_py()
            wsol_balance = await self.trading_core.get_wsol_balance_py()
            
            if wsol_balance > 0:
                # Unwrap all WSOL
                unwrap_cmd = f'spl-token unwrap {self.wsol_account}'
                if await self._run_command(unwrap_cmd):
                    await asyncio.sleep(2)
                    sol_balance = await self.trading_core.get_sol_balance_py()
            
            # Calculate wrap amount (95% of total SOL)
            wrap_amount = sol_balance * 0.95
            if wrap_amount > 0:
                wrap_cmd = f'spl-token wrap {wrap_amount:.4f}'
                if await self._run_command(wrap_cmd):
                    await asyncio.sleep(2)
                    
            # Final balance check
            final_sol = await self.trading_core.get_sol_balance_py()
            final_wsol = await self.trading_core.get_wsol_balance_py()
            
            self.balances["SOL"] = final_sol
            self.balances["WSOL"] = final_wsol
            
            logger.info(f"Rebalance complete - SOL: {final_sol:.4f}, WSOL: {final_wsol:.4f}")
            
        except Exception as e:
            logger.error(f"Rebalance error: {e}")
        finally:
            self.is_trading_paused = False

    async def _run_command(self, cmd: str) -> bool:
        """Execute shell command with timeout"""
        try:
            process = await asyncio.create_subprocess_shell(
                cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            try:
                stdout, stderr = await asyncio.wait_for(
                    process.communicate(),
                    timeout=30.0
                )
                
                if process.returncode == 0:
                    if stdout:
                        logger.info(f"Command output: {stdout.decode().strip()}")
                    return True
                else:
                    if stderr:
                        logger.error(f"Command error: {stderr.decode().strip()}")
                    return False
                    
            except asyncio.TimeoutError:
                process.kill()
                logger.error("Command timed out after 30 seconds")
                return False
                    
        except Exception as e:
            logger.error(f"Command execution error: {str(e)}")
            return False

    def show_performance(self):
        """Display trading performance metrics"""
        runtime = (datetime.now() - self.start_time).total_seconds()
        
        hours = int(runtime // 3600)
        minutes = int((runtime % 3600) // 60)
        seconds = int(runtime % 60)
        
        logger.system(f"""
🏁 Trading Performance Report
============================
Runtime: {hours:02d}:{minutes:02d}:{seconds:02d}

Trade Statistics:
----------------
Opportunities Found: {self.trades_found}
Trades Executed: {self.trades_executed}
Successful Trades: {self.successful_trades}
Success Rate: {(self.successful_trades/max(1,self.trades_executed)*100):.1f}%

Profit Metrics:
--------------
Total Profit: {self.total_profit:.6f} SOL
Profit per Hour: {(self.total_profit/(runtime/3600)):.6f} SOL/hr
Average Profit per Trade: {(self.total_profit/max(1,self.successful_trades)):.6f} SOL

Current Balances:
----------------
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
============================
""")

    async def run(self):
        """Start bot with all monitoring systems"""
        try:
            # Check initial balances
            await self.check_initial_balances()
            
            # Start monitoring systems
            balance_task = asyncio.create_task(self.monitor_balances())
            mempool_task = asyncio.create_task(self.process_mempool())
            
            # Wait for tasks
            await asyncio.gather(balance_task, mempool_task)
        except asyncio.CancelledError:
            logger.system("Tasks cancelled")
        except Exception as e:
            logger.error(f"Task error: {e}")
        finally:
            self.show_performance()
            await self.cleanup()

    async def cleanup(self):
        """Cleanup resources"""
        self.active = False
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Cleanup completed")

async def main():
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    try:
        bot = MEVBot()
        await bot.run()
    except KeyboardInterrupt:
        logger.system("Bot stopping gracefully...")
    except Exception as e:
        logger.error(f"Fatal error: {e}")
    finally:
        logger.system("Bot stopped")

if __name__ == "__main__":
    asyncio.run(main())