import asyncio
import logging
import yaml
import torch
import torch.amp
import websockets
import json
import base64
from datetime import datetime
from typing import Dict, Optional
from solana_rust_bot import QuantumTrader
import platform

# Set the event loop policy for Windows
if platform.system() == "Windows":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

class RaydiumSPLTrader:
    def __init__(self, config_path: str):
        """
        Initialize the RaydiumSPLTrader with the provided config file.
        """
        # Load the configuration file
        with open(config_path, 'r') as f:
            self.config = yaml.safe_load(f)

        # Setup logging and CUDA
        self.setup_logging()
        self.setup_cuda()

        # Initialize the trader
        self.trader = QuantumTrader(
            keypair_path="C:/solana_rust_bot/id.json",
            priority_fee=50000
        )

        # Constants
        self.RAYDIUM_AMM = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"
        self.BASE_UNIT = 4294967296  # 64-byte engine constant
        self.CHUNK_SIZE = 64

        # Trading parameters
        self.position_size = 0.05
        self.min_volume = 5000.0
        self.min_bs_ratio = 2.0
        self.min_liquidity = 40000.0

        # Stats
        self.trades_executed = 0
        self.pools_scanned = 0

        # Log wallet address
        self.logger.info(f"Wallet loaded: {self.trader.get_wallet_address()}")

    def setup_logging(self):
        """
        Setup logging handlers for console and file.
        """
        # Create logger
        self.logger = logging.getLogger('RaydiumSPL')
        self.logger.setLevel(logging.INFO)

        # Create formatters and add handlers
        formatter = logging.Formatter('%(asctime)s - %(message)s')
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)

        file_handler = logging.FileHandler(f'raydium_trades_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

    def setup_cuda(self):
        """
        Setup CUDA if available.
        """
        if torch.cuda.is_available():
            self.device = torch.device("cuda:0")
            torch.cuda.set_device(self.device)
            torch.cuda.empty_cache()
            self.logger.info(f"CUDA initialized: {torch.cuda.get_device_name()}")

    @torch.amp.autocast('cuda')  # Updated decorator
    def analyze_pool_data(self, data: bytes) -> Optional[Dict]:
        """
        Analyze pool data and extract relevant metrics.
        
        Returns None if no suitable metrics found or on error.
        """
        try:
            metrics = {}
            for i in range(0, len(data), self.CHUNK_SIZE):
                chunk = data[i:i+self.CHUNK_SIZE]
                if len(chunk) != self.CHUNK_SIZE:
                    continue  # Skip if chunk is not of the expected size
                
                # Extract pool metrics
                base_reserve = int.from_bytes(chunk[0:8], 'little') / self.BASE_UNIT
                quote_reserve = int.from_bytes(chunk[8:16], 'little') / self.BASE_UNIT
                volume = int.from_bytes(chunk[16:24], 'little') / self.BASE_UNIT
                token_address = chunk[24:56].hex()
                
                # Skip if base or quote reserve is zero
                if base_reserve == 0 or quote_reserve == 0:
                    continue
                
                price = quote_reserve / base_reserve
                bs_ratio = base_reserve / quote_reserve
                liquidity = min(base_reserve, quote_reserve)
                
                # Check trading conditions
                if volume >= self.min_volume and bs_ratio >= self.min_bs_ratio and liquidity >= self.min_liquidity:
                    metrics = {
                        'address': token_address,
                        'price': price,
                        'volume': volume,
                        'bs_ratio': bs_ratio,
                        'liquidity': liquidity
                    }
                    
                    self.logger.info(
                        f"\nPool Metrics:"
                        f"\nToken: {token_address}"
                        f"\nPrice: ${price:.6f}"
                        f"\nVolume: {volume:,.2f}"
                        f"\nB/S Ratio: {bs_ratio:.2f}"  # Fixed formatting
                        f"\nLiquidity: {liquidity:,.2f}"
                    )
                    break
            
            return metrics
            
        except Exception as e:
            self.logger.error(f"Analysis error: {str(e)}")
            return None

    async def execute_trade(self, metrics: Dict):
        """
        Execute a trade based on the provided metrics.
        """
        try:
            self.logger.info(f"\nExecuting trade for {metrics['address']}")
        
            # Execute direct swap
            signature = await self.trader.execute_swap(
                metrics['address'],
                self.position_size,
                True,
                50000
            )
        
            if signature:
                self.trades_executed += 1
                self.logger.info(f"Trade executed: {signature}")
            
                # Execute exit trade if bs_ratio is high
                if metrics['bs_ratio'] > 3.0:
                    await asyncio.sleep(2)
                    sell_sig = await self.trader.execute_swap(
                        metrics['address'],
                        self.position_size,
                        False,
                        50000
                    )
                    if sell_sig:
                        self.logger.info(f"Exit trade executed: {sell_sig}")

        except Exception as e:
            self.logger.error(f"Trade execution error: {str(e)}")


    async def monitor_pools(self):
        """
        Monitor Raydium pools and execute trades based on conditions.
        """
        self.logger.info("\nStarting Raydium pool monitoring...")
        
        retry_count = 0
        
        while True:
            try:
                # Connect to the websocket
                async with websockets.connect(
                    self.config['helius']['ws_endpoint'],
                    extra_headers={'Authorization': f"Bearer {self.config['helius']['api_key']}"},
                    ping_interval=30,
                    ping_timeout=10,
                    close_timeout=5
                ) as ws:
                    
                    # Subscribe to Raydium AMM
                    await ws.send(json.dumps({
                        "jsonrpc": "2.0",
                        "id": "raydium-amm",
                        "method": "programSubscribe",
                        "params": [
                            self.RAYDIUM_AMM,
                            {"encoding": "base64", "commitment": "confirmed"}
                        ]
                    }))
                    
                    response = await ws.recv()
                    if json.loads(response).get('result'):
                        self.logger.info("Connected to Raydium feed")
                    
                    while True:
                        # Receive messages
                        msg = await ws.recv()
                        data = json.loads(msg)
                        
                        # Process pool data
                        if "params" in data and "result" in data["params"]:
                            self.pools_scanned += 1
                            
                            # Log stats every 100 pools scanned
                            if self.pools_scanned % 100 == 0:
                                self.logger.info(
                                    f"Stats: {self.pools_scanned} pools scanned | "
                                    f"{self.trades_executed} trades executed"
                                )
                            
                            # Decode and analyze pool data
                            pool_data = base64.b64decode(data["params"]["result"]["value"]["account"]["data"][0])
                            metrics = self.analyze_pool_data(pool_data)
                            
                            # Execute trade if suitable metrics found
                            if metrics:
                                await self.execute_trade(metrics)
                            
            except websockets.exceptions.ConnectionClosed:
                retry_count += 1
                wait_time = min(30, retry_count * 3)
                self.logger.warning(f"Connection closed, retrying in {wait_time} seconds...")
                await asyncio.sleep(wait_time)
                
            except Exception as e:
                self.logger.error(f"Monitor error: {str(e)}")
                await asyncio.sleep(1)

    def start(self):
        """
        Start the RaydiumSPLTrader.
        """
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(self.monitor_pools())
        except KeyboardInterrupt:
            self.logger.info("\nShutting down gracefully...")
        finally:
            loop.close()
            if torch.cuda.is_available():
                torch.cuda.empty_cache()

if __name__ == "__main__":
    trader = RaydiumSPLTrader("config.yaml")
    trader.start()   ADD MAXIMUM LOGGING PLEASE