import os
import json
import asyncio
import base64
import base58
from datetime import datetime
import websockets
import torch
from typing import Optional
from dataclasses import dataclass
from dotenv import load_dotenv
from solana_logger import logger
from config_loader import config, TradeParams, TradingCore

# Load environment variables
load_dotenv()

@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):
        """Initialize bot with enhanced safety features"""
        # Initialize critical values first
        self.wsol_account = config.get("WSOL_TOKEN_ACCOUNT")
        self.wallet = config.get("SOLANA_PUBLIC_KEY")
        self.WSOL_MINT = "So11111111111111111111111111111111111111112"
        
        # Continue with remaining initialization
        self.load_config()
        self.init_components()
        self.init_trade_params()
        self.reset_performance_metrics()
        self.setup_safety_features()
        self._setup_cuda()
        self.log_startup()

    def setup_safety_features(self):
        """Initialize safety and monitoring features"""
        self.trade_lock = asyncio.Lock()
        self.active = True
        self.active_trades = 0
        self.MAX_ACTIVE_TRADES = 1
        self.last_profit_log = datetime.now()
        self.profit_log_interval = 300  # Log profits every 5 minutes
        
        # Track successful trades for auto risk management
        self.recent_trades = []
        self.max_trades_per_hour = 30
        self.min_success_rate = 0.7  # 70% success rate required
        
        # WSOL setup tracking
        self.known_atas = set()
        self.wsol_checked = False

    def load_config(self):
        """Load configuration with safety checks"""
        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_PROGRAM_ID")

        if not all([self.ws_url, self.api_key, self.program_id, self.wallet]):
            raise ValueError("Missing critical configuration parameters")

    def init_components(self):
        """Initialize core components with error handling"""
        try:
            network_config = config.get_network_config()
            self.trading_core = TradingCore(
                rpc_url=network_config["rpc_url"],
                ws_url=network_config["ws_url"],
                api_key=network_config["api_key"],
                public_key=self.wallet,
                wsol_token_account=self.wsol_account
            )
            logger.system("Core components initialized successfully")
        except Exception as e:
            raise RuntimeError(f"Failed to initialize components: {e}")

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

            # Basic validation
            if len(raw_data) < 40:
                return None

            # Extract instruction and amount
            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], "little") / 1e9

            # Validate trade size
            if instruction != 0x03 or not (self.MIN_VICTIM_SIZE <= amount <= self.MAX_VICTIM_SIZE):
                return None

            # Extract token information
            token_a_bytes = raw_data[40:72] if len(raw_data) >= 72 else raw_data[-32:]
            token_b_bytes = raw_data[72:104] if len(raw_data) >= 104 else None

            token_a_mint = base58.b58encode(token_a_bytes).decode()
            token_b_mint = self.WSOL_MINT if token_b_bytes is None else base58.b58encode(token_b_bytes).decode()

            # Calculate potential profit
            profit = self.calculate_profit(amount)
            if profit <= 0:
                return None

            # Track this transaction
            self.processed_txs.add(tx_id)

            # Create trade opportunity
            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

    def init_trade_params(self):
        """Initialize trading parameters with safety limits"""
        trade_config = config.get_trade_config()
        self.POSITION_SIZE = min(trade_config["front_run_amount"], 0.5)  # Max 0.5 SOL per trade
        self.MIN_VICTIM_SIZE = trade_config["min_victim_size"]
        self.MAX_VICTIM_SIZE = min(trade_config["max_victim_size"], 320.0)  # Cap max size
        self.MIN_PROFIT = max(trade_config["min_profit"], 0.002)  # Minimum 0.002 SOL profit
        self.LAMPORTS_PER_SOL = 1_000_000_000
        self.processed_txs = set()

    def reset_performance_metrics(self):
        """Reset performance metrics before starting operations"""
        self.trades_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = datetime.now()

        # 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

    def _setup_cuda(self):
        """Configure CUDA with optimal settings"""
        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}
====================
"""
        )

    async def check_wsol_setup(self):
        """Check the WSOL setup for trading"""
        if self.wsol_checked:
            return
        await self.check_initial_balances()
        self.known_atas.add(self.WSOL_MINT)
        self.wsol_checked = True

    async def check_initial_balances(self):
        """Check initial balances via RPC"""
        try:
            sol_balance = await self.trading_core.get_sol_balance()
            wsol_balance = await self.trading_core.get_wsol_balance()

            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}")

        except Exception as e:
            logger.error(f"Balance check error: {e}")

    def calculate_profit(self, amount: float) -> float:
        """Calculate potential profit with enhanced risk management"""
        with torch.cuda.stream(self.cuda_stream):
            amount_tensor = torch.tensor(amount, device="cuda")
            
            # Progressive profit rates based on amount
            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

            # Factor in all costs
            entry_impact = 0.001 * amount_tensor
            exit_impact = entry_impact
            priority_fee = 0.002
            safety_margin = 0.001  # Added safety margin

            net_profit = (amount_tensor * profit_rate) - (
                entry_impact + exit_impact + priority_fee + safety_margin
            )
            
            return float(net_profit) if net_profit > self.MIN_PROFIT else 0.0

    async def monitor_balances(self):
        """Monitor SOL and WSOL balances via WebSocket"""
        retry_count = 0
        max_retries = 3

        while True:
            try:
                logger.info("Connecting to balance monitor...")
                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,
                    close_timeout=60
                ) as ws:
                    retry_count = 0  # Reset retry count
                    await self._handle_balance_updates(ws)

            except websockets.ConnectionClosed:
                retry_count += 1
                if retry_count > max_retries:
                    logger.error("Max reconnection attempts reached")
                    raise
                wait_time = 2 ** retry_count
                logger.warning(f"Balance monitor disconnected, retrying in {wait_time}s...")
                await asyncio.sleep(wait_time)
            except Exception as e:
                logger.error(f"Balance monitor error: {e}")
                await asyncio.sleep(1)

    async def _handle_balance_updates(self, ws):
        """Handle balance update messages"""
        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"}
                ]
            }))

        while True:
            try:
                msg = await ws.recv()
                data = json.loads(msg)
                await self._process_balance_update(data)
            except Exception as e:
                logger.error(f"Balance update processing error: {e}")
                raise

    async def _process_balance_update(self, data):
        """Process balance update data"""
        try:
            if "result" in data:
                # Handle subscription confirmation
                acc_type = 'SOL' if data['id'] == 1 else 'WSOL'
                logger.info(f"{acc_type} subscription confirmed: {data['result']}")
            elif "method" in data and data["method"] == "accountNotification":
                params = data["params"]
                subscription = params["subscription"]
                value = params["result"]["value"]
                
                # Update balance
                lamports = int(value["lamports"])
                balance = lamports / 1e9
                
                acc_type = 'SOL' if subscription == 1 else 'WSOL'
                old_balance = self.balances[acc_type]
                self.balances[acc_type] = balance
                
                # Log significant changes
                if balance != old_balance:
                    change = balance - old_balance
                    logger.info(f"{acc_type} Balance: {balance:.4f} ({'+' if change >= 0 else ''}{change:.4f})")
                    
                    # Check for low balances
                    if acc_type == 'WSOL' and balance < self.POSITION_SIZE:
                        logger.warning(f"WSOL balance below position size: {balance:.4f}")
                    elif acc_type == 'SOL' and balance < self.min_sol_balance:
                        logger.warning(f"Low SOL balance: {balance:.4f}")
                
        except Exception as e:
            logger.error(f"Error processing balance update: {e}")

    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 self._handle_mempool_messages(ws)

            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_mempool_messages(self, ws):
        """Handle mempool messages"""
        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)
                await self._process_mempool_message(data)
            except Exception as e:
                logger.error(f"Message processing error: {e}")

    async def _process_mempool_message(self, data):
        """Process individual mempool messages"""
        if "params" not in data:
            return

        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)

    async def execute_trade(self, trade: TradeOpportunity) -> bool:
        """Execute trade with guaranteed execution or no entry"""
        if self.balances["WSOL"] < self.POSITION_SIZE:
            logger.warning(
                f"Insufficient WSOL balance: {self.balances['WSOL']:.4f}. Required: {self.POSITION_SIZE:.4f}."
            )
            return False

        async with self.trade_lock:
            try:
                if self.active_trades >= 1:  # Only check active trades, not time
                    logger.warning("Trade already in progress")
                    return False

                self.active_trades += 1
                position_lamports = int(self.POSITION_SIZE * self.LAMPORTS_PER_SOL)

                logger.trade(
                    f"""
🎯 Trade Found:
Size: {trade.amount:.4f} SOL
Position: {self.POSITION_SIZE} SOL ({position_lamports} lamports)
Token A: {trade.token_a_mint}
Token B: {trade.token_b_mint}
Est. Profit: {trade.net_profit:.6f} SOL
TX ID: {trade.transaction_id}
"""
                )

                # Full Raydium accounts setup
                raydium_accounts = [
                    self.program_id,
                    config.get("MARKET_AMM_AUTHORITY"),
                    config.get("MARKET_AMM_OPEN_ORDERS"),
                    config.get("MARKET_AMM_TARGET"),
                    config.get("MARKET_POOL_TOKEN_ACCOUNT"),
                    config.get("MARKET_SERUM_MARKET"),
                    config.get("MARKET_SERUM_BIDS"),
                    config.get("MARKET_SERUM_ASKS"),
                    config.get("MARKET_EVENT_QUEUE"),
                    config.get("MARKET_BASE_VAULT"),
                    config.get("MARKET_QUOTE_VAULT"),
                    config.get("BASE_VAULT_SIGNER"),
                    config.get("QUOTE_VAULT_SIGNER")
                ]

                # Validate all accounts are present
                if None in raydium_accounts or len(raydium_accounts) != 13:
                    logger.error("Missing required Raydium accounts")
                    return False

                # Entry trade parameters
                entry_params = TradeParams(
                    accounts=raydium_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,
                )

                # Execute entry trade
                entry_result = await self.trading_core.execute_trade_py(entry_params)
                if entry_result:
                    await asyncio.sleep(0.0001)  # Small delay between trades

                    # Exit trade parameters
                    exit_params = TradeParams(
                        accounts=raydium_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,
                    )

                    # Execute exit trade
                    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 Completed:
Entry TX: {entry_result}
Exit TX: {exit_result}
Original TX: {trade.transaction_id}
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

                return False

            except Exception as e:
                logger.error(f"Trade execution error: {e}")
                return False
            finally:
                self.active_trades -= 1

    async 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) if runtime > 0 else 0):.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):
        """Run the bot with proper task management"""
        try:
            await self.check_initial_balances()
            
            tasks = [
                asyncio.create_task(self.monitor_balances()),
                asyncio.create_task(self.process_mempool()),
                asyncio.create_task(self.periodic_maintenance())
            ]
            
            await asyncio.gather(*tasks)
            
        except asyncio.CancelledError:
            logger.system("Tasks cancelled")
        except Exception as e:
            logger.error(f"Task error: {e}")
        finally:
            await self.cleanup()

    async def periodic_maintenance(self):
        """Perform periodic maintenance tasks"""
        while self.active:
            try:
                # Log performance periodically
                if (datetime.now() - self.last_profit_log).seconds >= self.profit_log_interval:
                    await self.show_performance()
                    self.last_profit_log = datetime.now()
                
                # Clean up old processed transactions
                current_time = datetime.now()
                self.processed_txs = {
                    tx for tx in self.processed_txs 
                    if not hasattr(tx, 'timestamp') or (current_time - tx.timestamp).total_seconds() < 3600
                }
                
                await asyncio.sleep(60)
                
            except Exception as e:
                logger.error(f"Maintenance error: {e}")
                await asyncio.sleep(60)

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

async def main():
    """Main entry point with proper setup"""
    if os.name == "nt":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    try:
        print("\n" + "="*80)
        print("Solana MEV Bot - Supporting St. Jude Children's Research Hospital")
        print("="*80 + "\n")
        
        bot = MEVBot()
        await bot.run()
    except KeyboardInterrupt:
        logger.system("Bot stopping gracefully...")
    except Exception as e:
        logger.error(f"Fatal error: {e}")
    finally:
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Bot stopped")

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