import asyncio
import json
import time
import base64
import base58
import traceback
import traceback
from pathlib import Path
from solana.rpc.async_api import AsyncClient
from solana.publickey import PublicKey
from solana.transaction import Transaction
from solana.keypair import Keypair
from solana.rpc.commitment import Commitment
from solana.rpc.types import TxOpts
from spl.token.instructions import (
    get_associated_token_address,
    create_associated_token_account,
)
from solana_rust_bot import TradingCore, TradeParams
from solana_logger import logger
from balance_manager import BalanceManager
from config_loader import config
import websockets
import torch
import os
from base58 import b58encode

SOLANA_VERSION = "solana-client/1.18.23 (src:aeb3a2e1; feat:4215500110, client:SolanaLabs)"
WEBSOCKET_CONFIG = {
    "ping_interval": 0.5,
    "ping_timeout": 5,
    "compression": None,
    "max_size": 2**23,
    "close_timeout": 1.0,
    "max_queue": 2**16,
    "read_limit": 2**16,
}

# Predefined Solana Program IDs
PROGRAM_IDS = {
    "token": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
    "system": "11111111111111111111111111111111",
    "associated_token": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}

class QuantumTrader:
    def __init__(self):
        logger.system("🚀 Initializing Quantum Trader")
        
        # Load configurations first
        self.network_config = config.get_network_config()
        self.trade_config = config.get_trade_config()
        self.perf_config = config.get_performance_config()
        
        # Initialize components
        self._setup_cuda()
        self._init_core()
        
        # Setup state management
        self.execution_lock = asyncio.Lock()
        self.in_trade = False
        self.ata_cache = {}
        self.ws = None
        self.reconnect_delay = 1.0
        self.max_reconnect_delay = 30.0
        
        logger.system("✨ Quantum Trader Initialized!")

    def _setup_cuda(self):
        """Configure CUDA for optimal performance."""
        try:
            torch.cuda.init()
            torch.backends.cuda.matmul.allow_tf32 = self.perf_config.get("allow_tf32", True)
            torch.backends.cudnn.benchmark = True
            torch.cuda.set_device(0)
            torch.cuda.empty_cache()

            memory_fraction = self.perf_config.get("gpu_memory_fraction", 0.8)
            torch.cuda.set_per_process_memory_fraction(memory_fraction)

            self.stream = torch.cuda.Stream(
                priority=self.perf_config.get("stream_priority", 0)
            )
            logger.system(f"💻 CUDA Initialized | Memory: {memory_fraction * 100}%")
        except Exception as e:
            logger.error(f"CUDA initialization failed: {e}")
            raise

    def _init_core(self):
        """Initialize core trading components."""
        try:
            # Load keypair
            self.keypair_bytes = config.get_keypair_bytes()
            self.keypair = Keypair.from_secret_key(self.keypair_bytes)

            # Initialize trading core with all required program IDs
            self.trading_core = TradingCore(
                rpc_url=self.network_config["rpc_url"],
                keypair_bytes=self.keypair_bytes,
                raydium_program_id=config.get("raydium"),
                token_program_id=PROGRAM_IDS["token"],
                system_program_id=PROGRAM_IDS["system"],
                associated_token_program_id=PROGRAM_IDS["associated_token"]
            )

            # Initialize balance manager and client
            self.balance_manager = BalanceManager()
            self.client = AsyncClient(
                self.network_config["rpc_url"], 
                commitment=Commitment("confirmed")
            )

            logger.info(f"Trading core initialized for wallet: {str(self.keypair.public_key)}")

        except Exception as e:
            logger.error(f"Core initialization error: {e}")
            raise

    async def _create_and_get_ata(self, token_mint: str) -> str:
    """Create ATA if needed and return address."""
    try:
        # Check cache first
        if token_mint in self.ata_cache:
            return self.ata_cache[token_mint]

        wallet_pubkey = self.keypair.public_key
        token_pubkey = PublicKey(token_mint)
        ata = get_associated_token_address(wallet_pubkey, token_pubkey)

        # Check if account exists with commitment
        account_info = await self.client.get_account_info(
            ata, commitment=Commitment("confirmed")
        )
        if account_info.value is not None:
            self.ata_cache[token_mint] = str(ata)
            return str(ata)

        # Create ATA instruction
        create_ata_ix = create_associated_token_account(
            payer=wallet_pubkey,
            owner=wallet_pubkey,
            mint=token_pubkey
        )

        max_retries = 3
        for attempt in range(max_retries):
            try:
                # Get fresh blockhash
                resp = await self.client.get_latest_blockhash(commitment=Commitment("finalized"))
                blockhash = resp.value.blockhash

                # Build transaction
                tx = Transaction()
                tx.recent_blockhash = blockhash
                tx.fee_payer = wallet_pubkey
                tx.add(create_ata_ix)
                tx.sign(self.keypair)

                # Send with minimal options
                opts = TxOpts(
                    skip_preflight=False,
                    preflight_commitment=Commitment("confirmed")
                )

                logger.debug(f"Sending ATA creation attempt {attempt + 1}/{max_retries}")
                sig = await self.client.send_transaction(tx.serialize(), opts=opts)
                logger.debug(f"Transaction sent, signature: {sig.value}")

                # Wait for confirmation
                await self.client.confirm_transaction(
                    sig.value,
                    commitment=Commitment("confirmed")
                )

                # Verify ATA was created
                account_info = await self.client.get_account_info(ata)
                if account_info.value is not None:
                    logger.info(f"Created ATA {str(ata)} for token {token_mint}")
                    self.ata_cache[token_mint] = str(ata)
                    return str(ata)

            except Exception as e:
                if "insufficient funds" in str(e).lower():
                    logger.error("Insufficient funds for ATA creation")
                    return None

                logger.debug(f"ATA creation attempt {attempt + 1} failed: {e}")
                if attempt < max_retries - 1:
                    await asyncio.sleep(1 * (2 ** attempt))
                    continue
                raise

        raise Exception("Failed to create ATA after all retries")

    except Exception as e:
        logger.error(f"ATA creation failed: {str(e)}")
        logger.debug(f"Full error traceback: {traceback.format_exc()}")
        return None 
    def decode_raydium_data(self, raw_data: bytes) -> dict:
        """Decode Raydium instruction data."""
        try:
            if len(raw_data) < 9:
                return None

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

            # Validate size bounds
            if amount < self.trade_config["min_victim_size"] or amount > self.trade_config["max_victim_size"]:
                return None
            if instruction_type not in [0x03, 0x04]:
                return None

            # Extract token mint
            token_mint = None
            for offset in [9, 17]:
                if len(raw_data) >= offset + 32:
                    try:
                        token_bytes = raw_data[offset:offset + 32]
                        potential_mint = b58encode(token_bytes).decode("utf-8")
                        if len(potential_mint) in [32, 44]:
                            token_mint = potential_mint
                            break
                    except:
                        continue

            if not token_mint:
                return None

            # Calculate profit with CUDA
            with torch.cuda.stream(self.stream):
                position_size = self.trade_config["front_run_amount"]
                impact_multiplier = 1.2 if amount >= 20.0 else 1.0
                victim_impact = amount * 0.0015 * impact_multiplier
                our_impact = position_size * 0.002
                gas_cost = 0.002
                net_profit = (position_size * (victim_impact - our_impact)) - gas_cost

                if net_profit < self.trade_config["min_profit"]:
                    return None

            return {
                "victim_data": raw_data,
                "amount": amount,
                "token_mint": token_mint,
                "net_profit": float(net_profit),
                "instruction_type": instruction_type,
            }

        except Exception as e:
            logger.error(f"Decode error: {e}")
            return None

    async def execute_trade(self, trade_data: dict) -> bool:
        """Execute trade with proper ATA handling."""
        if self.in_trade or self.balance_manager.is_trading_paused:
            return False

        async with self.execution_lock:
            try:
                self.in_trade = True

                # Create ATA first
                user_token = await self._create_and_get_ata(trade_data["token_mint"])
                if not user_token:
                    return False

                # Account setup with proper ATA
                market_accounts = config.get_market_accounts()
                trade_accounts = {
                    "signer": config.get("wallet"),
                    "user": config.get("wallet"),
                    "user_token": str(user_token),
                    "user_wsol": config.get("wsol_token_account"),
                    "raydium_program": config.get("raydium"),
                    **market_accounts
                }

                # Verify accounts
                for key, value in trade_accounts.items():
                    if not value or value.lower() == "none":
                        logger.error(f"Missing required account: {key}")
                        return False

                position_size = self.trade_config["front_run_amount"]
                logger.trade(
                    f"""
🎯 Starting Trade:
Amount: {position_size} SOL
Token: {trade_data['token_mint']}
Expected Profit: {trade_data['net_profit']:.4f} SOL
"""
                )

                # Execute entry
                entry_params = TradeParams(
                    accounts=trade_accounts,
                    amount_in=position_size,
                    is_buy=True,
                    spl_token_mint=str(trade_data["token_mint"]),
                    victim_data=None,
                    reuse_blockhash=False
                )

                entry_sig = await self.trading_core.execute_trade_py(entry_params)
                if not entry_sig:
                    return False

                logger.trade(f"✅ Entry complete: {entry_sig}")

                # Quick exit attempts
                for i in range(3):
                    exit_params = TradeParams(
                        accounts=trade_accounts,
                        amount_in=position_size,
                        is_buy=False,
                        spl_token_mint=str(trade_data["token_mint"]),
                        victim_data=None,
                        reuse_blockhash=i == 0
                    )

                    exit_sig = await self.trading_core.execute_trade_py(exit_params)
                    if exit_sig:
                        logger.trade(
                            f"""
🎯 Trade Complete!
Entry: {entry_sig}
Exit: {exit_sig}
Profit: {trade_data['net_profit']:.4f} SOL
"""
                        )
                        return True

                    await asyncio.sleep(0.05)

                logger.error("Failed to complete exit")
                return False

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

    async def _process_message(self, msg: str):
        """Process incoming WebSocket messages."""
        try:
            data = json.loads(msg)
            if "params" not in data:
                return

            logs = (
                data["params"]
                .get("result", {})
                .get("value", {})
                .get("logs", [])
            )

            for log in logs:
                if "ray_log:" in log:
                    decoded = base64.b64decode(log.split("ray_log:")[1].strip())
                    trade_data = self.decode_raydium_data(decoded)
                    if trade_data:
                        await self.execute_trade(trade_data)

        except json.JSONDecodeError:
            logger.error("Failed to decode WebSocket message")
        except Exception as e:
            logger.error(f"Message processing error: {e}")

    async def start(self):
    """Start the quantum trader."""
    logger.system(
        f"""
🚀 QUANTUM TRADER STARTING
==================================================
⚡ Target Range: {self.trade_config["min_victim_size"]}-{self.trade_config["max_victim_size"]} SOL
💰 Position Size: {self.trade_config["front_run_amount"]} SOL
📊 Min Profit: {self.trade_config["min_profit"]} SOL
🔥 Priority Fee: {self.trade_config["priority_fee"] / 1_000_000} SOL
==================================================
"""
    )

    # Start balance manager
    balance_task = asyncio.create_task(self.balance_manager.start())

    try:
        while True:
            try:
                async with websockets.connect(
                    self.network_config["ws_url"],
                    extra_headers={
                        "api-key": self.network_config["api_key"],
                        "User-Agent": SOLANA_VERSION
                    },
                    **WEBSOCKET_CONFIG
                ) as self.ws:
                    logger.system("📡 Connected to Helius")
                    self.reconnect_delay = 1.0

                    # Subscribe to Raydium program
                    await self.ws.send(
                        json.dumps({
                            "jsonrpc": "2.0",
                            "id": 1,
                            "method": "logsSubscribe",
                            "params": [
                                {"mentions": [config.get("raydium")]},
                                {"commitment": "processed"},
                            ],
                        })
                    )

                    while True:
                        try:
                            msg = await asyncio.wait_for(
                                self.ws.recv(),
                                timeout=WEBSOCKET_CONFIG["ping_timeout"]
                            )
                            await self._process_message(msg)
                        except asyncio.TimeoutError:
                            # Send ping to check connection
                            pong = await self.ws.ping()
                            await asyncio.wait_for(
                                pong, 
                                timeout=WEBSOCKET_CONFIG["ping_timeout"]
                            )
                            continue

            except websockets.exceptions.ConnectionClosed:
                logger.warning(f"WebSocket disconnected, reconnecting in {self.reconnect_delay}s...")
                await asyncio.sleep(self.reconnect_delay)
                self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)

            except Exception as e:
                logger.error(f"WebSocket error: {e}")
                await asyncio.sleep(self.reconnect_delay)
                self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)

    except Exception as e:
        logger.error(f"Bot error: {e}")
        traceback.print_exc()
    finally:
        await self.stop()
        balance_task.cancel()
        try:
            await balance_task
        except asyncio.CancelledError:
            pass

    async def stop(self):
        """Gracefully stop the quantum trader."""
        logger.system("🛑 Stopping Quantum Trader")
        await self.balance_manager.cleanup()
        if self.ws:
            try:
                await asyncio.wait_for(self.ws.close(), timeout=1.0)
            except:
                pass

if __name__ == "__main__":
    try:
        # Enable detailed logging for debugging
        import logging
        logging.basicConfig(level=logging.DEBUG)
        logger.debug("Debug logging enabled")
        
        trader = QuantumTrader()
        asyncio.run(trader.start())
    except KeyboardInterrupt:
        logger.system("Trader stopped by user")
    except Exception as e:
        logger.error(f"Trader crashed: {str(e)}")
        traceback.print_exc() please fix spacing and add very good debug logging printed in terminal