import asyncio
import logging
import json
import websockets
import base64
import time
from pathlib import Path
from typing import Dict, Optional
from dataclasses import dataclass
import torch
from solana_rust_bot import TradingCore, TradingConfig
from vector_store import VectorStore

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s | %(levelname)s | %(message)s',
    handlers=[logging.FileHandler("trading.log"), logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

@dataclass
class Position:
    token_mint: str
    pool_address: str
    entry_price: float
    size: float
    entry_time: float
    take_profit: float
    stop_loss: float

class RaydiumTrader:
    def __init__(self, config_path: str):
        logger.debug("Initializing Raydium Trader...")
        self.config = self._load_config(config_path)
        self.setup_trading()
        self.setup_gpu()
        
        self.RAYDIUM_AMM = self.config['programs']['raydium']
        self.positions = {}
        self._position_lock = asyncio.Lock()
        self.running = True
        self.vector_store = VectorStore(self.config)
        logger.info("Vector store initialized for pool analysis")

    def _load_config(self, path: str) -> dict:
        logger.debug(f"Loading configuration from {path}...")
        with open(Path(path)) as f:
            config = json.load(f)
            logger.info("Configuration loaded successfully.")
            return config

    def setup_trading(self):
        logger.debug("Setting up trading...")
        config = TradingConfig()
        config.helius_endpoint = self.config['rpc']['helius']['endpoint']
        config.helius_api_key = self.config['rpc']['helius']['api_key']
        config.priority_fee_type = "helius"
        config.compute_limit = str(self.config['execution']['compute_limit'])
        config.skip_preflight = "true"
        self.trading = TradingCore(config)
        logger.info("Trading setup completed.")

    def setup_gpu(self):
        logger.debug("Setting up GPU...")
        if torch.cuda.is_available():
            self.device = torch.device("cuda")
            torch.backends.cudnn.benchmark = True
            self.price_tensor = torch.zeros(1000, device=self.device, dtype=torch.float32)
            logger.info(f"Using GPU: {torch.cuda.get_device_name()}")
        else:
            self.device = torch.device("cpu")
            self.price_tensor = torch.zeros(1000, dtype=torch.float32)
            logger.warning("Using CPU")

    def decode_pool_data(self, log: str) -> Optional[Dict]:
        logger.debug("Decoding pool data...")
        try:
            if "ray_log: " not in log:
                return None
            
            ray_log = log.split("ray_log: ")[1].strip()
            raw_data = base64.b64decode(ray_log)
            
            if len(raw_data) < 32:
                logger.warning("Decoded log data is too short.")
                return None

            base_offset = 16
            base_reserve = int.from_bytes(raw_data[base_offset:base_offset+8], 'little')
            quote_reserve = int.from_bytes(raw_data[base_offset+8:base_offset+16], 'little')
            SOL_DECIMALS = 1e9
            base_amount = base_reserve / SOL_DECIMALS
            quote_amount = quote_reserve / SOL_DECIMALS
            price = quote_amount / base_amount if base_amount > 0 else 0
            liquidity = base_amount * price

            pool_address = log.split()[2] if "ray_log:" in log else None
            
            logger.debug(f"Decoded pool data: price={price}, base_reserve={base_amount}, quote_reserve={quote_amount}, liquidity={liquidity}, pool_address={pool_address}")
            return {
                'price': price,
                'base_reserve': base_amount,
                'quote_reserve': quote_amount,
                'liquidity': liquidity,
                'pool_address': pool_address,
                'timestamp': time.time()
            }
        except Exception as e:
            logger.error(f"Decode error: {e}")
            return None

    async def analyze_market(self, pool_data: dict) -> dict:
        logger.debug("Analyzing market...")
        try:
            self.vector_store.add_pool_data(pool_data)
            logger.debug("Pool data added to vector store.")
            similar_pools = await self.vector_store.search_similar_pools(pool_data, k=3)
            logger.debug(f"Found {len(similar_pools)} similar pools.")

            with torch.no_grad():
                self.price_tensor = torch.roll(self.price_tensor, -1)
                self.price_tensor[-1] = pool_data['price']
                
                window = 100
                recent_prices = self.price_tensor[-window:]
                price_changes = recent_prices[1:] - recent_prices[:-1]
                
                buy_pressure = torch.sum(price_changes[price_changes > 0])
                sell_pressure = torch.abs(torch.sum(price_changes[price_changes < 0]))
                
                pressure_ratio = float(buy_pressure / (sell_pressure + 1e-8))
                momentum = float(torch.mean(price_changes[-20:]))
                
                return {
                    'pressure_ratio': pressure_ratio,
                    'momentum': momentum,
                    'similar_patterns': len(similar_pools),
                    'avg_similarity': sum(p['similarity'] for p in similar_pools) / max(len(similar_pools), 1)
                }
        except Exception as e:
            logger.error(f"Analysis error: {e}")
            return {}

    def should_enter_trade(self, pool_data: dict, market_analysis: dict) -> bool:
        logger.debug("Checking if trade should be entered...")
        try:
            if len(self.positions) >= self.config['trading']['position']['max_positions']:
                logger.warning("Max positions reached.")
                return False

            limits = self.config['trading']['limits']
            if pool_data['liquidity'] < limits['min_liquidity']:
                logger.warning("Liquidity below minimum limit.")
                return False

            impact = self.config['trading']['position']['size'] / pool_data['base_reserve']
            if impact > limits['max_price_impact']:
                logger.warning("Price impact too high.")
                return False

            if market_analysis.get('pressure_ratio', 0) < limits['min_pressure_ratio']:
                logger.warning("Pressure ratio below minimum threshold.")
                return False
                
            if market_analysis.get('momentum', 0) < limits['min_momentum']:
                logger.warning("Momentum below minimum threshold.")
                return False
                
            logger.info("Conditions met for entering trade.")
            return True
            
        except Exception as e:
            logger.error(f"Entry validation error: {e}")
            return False

    async def enter_position(self, pool_address: str, pool_data: dict) -> bool:
        logger.debug(f"Attempting to enter position for pool: {pool_address}")
        try:
            market_analysis = await self.analyze_market(pool_data)
            if not self.should_enter_trade(pool_data, market_analysis):
                logger.info("Trade entry conditions not met.")
                return False

            size = self.config['trading']['position']['size']
            slippage = self.config['execution']['slippage']
            params = {
                "pool": pool_address,
                "amount": size,
                "min_out": size * (1 - slippage)
            }

            signature = self.trading.execute_trade(params)
            if signature:
                take_profit_pct = self.config['trading']['position']['take_profit']
                stop_loss_pct = self.config['trading']['position']['stop_loss']
                
                position = Position(
                    token_mint=pool_data.get('token_mint', pool_address),
                    pool_address=pool_address,
                    entry_price=pool_data['price'],
                    size=size,
                    entry_time=time.time(),
                    take_profit=pool_data['price'] * (1 + take_profit_pct),
                    stop_loss=pool_data['price'] * (1 - stop_loss_pct)
                )
                self.positions[pool_address] = position
                logger.info(f"Entered position {pool_address[:8]} at {pool_data['price']:.4f}")
                return True

            logger.warning("Trade execution failed, no signature returned.")
            return False
        except Exception as e:
            logger.error(f"Entry error: {e}")
            return False

    async def exit_position(self, pool_address: str, current_price: float, reason: str):
        logger.debug(f"Attempting to exit position for pool: {pool_address}, reason: {reason}")
        try:
            position = self.positions[pool_address]
            params = {
                "pool": pool_address,
                "amount": position.size,
                "min_out": position.size * (1 - self.config['execution']['slippage'])
            }

            signature = self.trading.execute_trade(params)
            if signature:
                pnl = (current_price - position.entry_price) * position.size
                duration = time.time() - position.entry_time
                
                logger.info(f"""Exit ({reason}):
                Pool: {pool_address[:8]}
                Price: ${current_price:.4f}
                PnL: {pnl:.6f} SOL (${pnl * 110:.2f})
                Duration: {duration:.1f}s""")
                
                self.positions.pop(pool_address, None)
            else:
                logger.error("Failed to exit position, no signature returned.")

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

    async def process_transaction(self, tx_data: dict):
        logger.debug("Processing transaction data...")
        try:
            logs = tx_data.get('params', {}).get('result', {}).get('value', {}).get('logs', [])
            if any(self.RAYDIUM_AMM in log for log in logs):
                for log in logs:
                    if "Program log: " in log and "ray_log: " in log:
                        logger.debug(f"Processing log: {log[:100]}...")
                        pool_data = self.decode_pool_data(log)
                        if pool_data and pool_data['pool_address']:
                            await self.update_positions(pool_data['pool_address'], pool_data)

        except Exception as e:
            logger.error(f"Transaction processing error: {e}")

    async def update_positions(self, pool_address: str, pool_data: dict):
        logger.debug(f"Updating positions for pool: {pool_address}")
        try:
            current_price = pool_data['price']
            
            if pool_address in self.positions:
                pos = self.positions[pool_address]
                time_in_trade = time.time() - pos.entry_time
                
                logger.debug(f"Position found for pool {pool_address}: {pos}")
                if current_price >= pos.take_profit:
                    logger.info(f"Current price {current_price:.4f} reached take profit level.")
                    await self.exit_position(pool_address, current_price, "take_profit")
                elif current_price <= pos.stop_loss:
                    logger.info(f"Current price {current_price:.4f} reached stop loss level.")
                    await self.exit_position(pool_address, current_price, "stop_loss")
                elif time_in_trade > 300:  # 5 minute timeout
                    logger.info(f"Timeout reached for position after {time_in_trade} seconds.")
                    await self.exit_position(pool_address, current_price, "timeout")
            else:
                await self.enter_position(pool_address, pool_data)

        except Exception as e:
            logger.error(f"Position update error: {e}")

    async def run(self):
        logger.debug("Starting trader run loop...")
        ws_url = self.config['rpc']['helius']['websocket']
        
        logger.info(f"""Starting Raydium Trader:
        Size: {self.config['trading']['position']['size']} SOL
        Take Profit: {self.config['trading']['position']['take_profit']*100}%
        Stop Loss: {self.config['trading']['position']['stop_loss']*100}%
        Max Positions: {self.config['trading']['position']['max_positions']}""")

        while self.running:
            try:
                async with websockets.connect(ws_url) as ws:
                    subscribe_msg = {
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [self.RAYDIUM_AMM]},
                            {"commitment": "processed"}
                        ]
                    }
                    
                    await ws.send(json.dumps(subscribe_msg))
                    logger.info("Connected to Helius websocket")
                    
                    while self.running:
                        msg = await ws.recv()
                        await self.process_transaction(json.loads(msg))

            except websockets.exceptions.ConnectionClosed:
                logger.warning("WebSocket connection closed. Attempting to reconnect...")
                await asyncio.sleep(2)

            except Exception as e:
                logger.error(f"Connection error: {e}")
                await asyncio.sleep(1)

    async def stop(self):
        logger.info("Stopping trader...")
        self.running = False
        async with self._position_lock:
            for addr in list(self.positions.keys()):
                await self.exit_position(addr, self.positions[addr].entry_price, "shutdown")

if __name__ == "__main__":
    trader = RaydiumTrader("config.json")
    try:
        asyncio.run(trader.run())
    except KeyboardInterrupt:
        logger.info("Trader stopped by user")
    except Exception as e:
        logger.critical(f"Fatal error: {e}")  fully debug log this with extreme maximum effect debugging