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

# Constants
LAMPORTS_PER_SOL = 1_000_000_000
WSOL_MINT = "So11111111111111111111111111111111111111112"
GAS_PER_TRADE = 0.00075  # Gas cost per transaction (entry + exit)

@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 QuantumMEV:
    def __init__(self):
        self.load_config()  # Load configs
        self.init_components()  # Initialize core components
        self.init_trade_params()  # Set up trading parameters
        self.reset_performance_metrics()  # Reset performance metrics
        self.trade_lock = asyncio.Lock()  # Lock for trade execution
        self.active = True  # Bot activity state
        self.active_trades = 0  # Active trade counter
        self.MAX_ACTIVE_TRADES = 1  # Max active trades allowed
        self._setup_cuda()  # Setup CUDA for GPU calculations
        self.log_startup()  # Log startup info

        # State for WSOL setup
        self.known_atas = set()  # Track ATAs we've verified
        self.wsol_checked = False  # Track if WSOL setup has been done

    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")
        self.wallet = config.get("wallet")
        
        # Define WSOL mint address
        self.WSOL_MINT = WSOL_MINT  # Specify the WSOL mint

    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 init_trade_params(self):
        """Initialize trading parameters from configuration"""
        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"]
        
        # Track processed transactions
        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
        self.balances = {"SOL": 0.0, "WSOL": 0.0}
        balance_config = config.get_balance_config()
        self.min_sol_balance = balance_config.get("min_sol_balance", 0.0)
        self.min_wsol_balance = balance_config.get("min_wsol_balance", 0.0)
        self.is_trading_paused = False

    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=-3)
        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()  # Check balances on initialization
        self.known_atas.add(WSOL_MINT)  # Track the 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 GPU acceleration"""
        with torch.cuda.stream(self.cuda_stream):
            try:
                amount_tensor = torch.tensor(amount, device='cuda')
                exit_value = amount_tensor * 0.97  # Reflect slippage
                return float(exit_value.cpu()) - amount  # Adjust for slippage
            except Exception as e:
                logger.error(f"Profit calculation error: {e}")
                return 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 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 = WSOL_MINT if token_b_bytes is None else 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 a trade"""
        if not self.wsol_checked:
            await self.check_wsol_setup()  # Ensure WSOL is set up before trading

        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 >= self.MAX_ACTIVE_TRADES:
                    logger.warning("Max active trades reached, skipping trade execution.")
                    return False

                self.active_trades += 1
                position_lamports = int(self.POSITION_SIZE * 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}
""")

                # Prepare parameters for the entry trade
                entry_params = TradeParams(
                    accounts=config.get_market_accounts(),
                    amount_in=int(position_lamports * 1.03),  # Apply 3% slippage for the buy entry
                    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  # Set to False to specify amount_in
                )

                entry_result = await self.trading_core.execute_trade_py(entry_params)
                if entry_result:
                    await asyncio.sleep(0.0001)  # Allow time for the transaction to settle
                    
                    # Fetch balance of the token being sold
                    balance = await self.trading_core.get_token_balance(trade.token_a_mint)
                    if balance is None or balance <= 0:
                        logger.error("Invalid balance after entry trade")
                        return False
                    
                    # Apply slippage for the exit trade
                    slippage_exit_amount = int(balance * 0.97)  # Reduce by 3%

                    exit_params = TradeParams(
                        accounts=config.get_market_accounts(),
                        amount_in=slippage_exit_amount,
                        is_buy=False,
                        token_a_mint=trade.token_a_mint,
                        token_b_mint=trade.token_b_mint,
                        victim_data=None,
                        sell_all=False  # Use amount_in instead of sell_all to specify the amount to sell
                    )
                    
                    exit_result = await self.trading_core.execute_trade_py(exit_params)
                    if exit_result:
                        logger.trade(f"💫 Exit Trade: Signature: {exit_result}")

                return True

            except Exception as e:
                logger.error(f"Trade execution error: {e}")
                return False
            finally:
                self.active_trades -= 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}
                ) 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:
                                    logger.info(f"Found opportunity: {log}")  
                                    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:
                                            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 run(self):
        """Start monitoring and trading operations"""
        try:
            await self.check_initial_balances()
            mempool_task = asyncio.create_task(self.process_mempool())
            await mempool_task
        except Exception as e:
            logger.error(f"Error during bot execution: {e}")
        finally:
            logger.info("Shutting down the bot.")

async def main():
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    try:
        bot = QuantumMEV()
        await bot.run()  # Running the bot's main function
    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())  # Correctly trigger the event loop and start the bot.