import asyncio
import logging
import json
import websockets
from dataclasses import dataclass
from typing import Dict, Optional, Set
from decimal import Decimal
import time
import base64

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s.%(msecs)03d [%(levelname)s] [%(funcName)s:%(lineno)d] %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler('monitor.log')
    ]
)
logger = logging.getLogger(__name__)

@dataclass
class PoolState:
    base_reserve: Decimal
    quote_reserve: Decimal
    timestamp: float
    token_mint: str
    price_usd: Optional[float] = None
    liquidity: Optional[float] = None

class RaydiumMonitor:
    def __init__(self, helius_ws: str):
        self.helius_ws = helius_ws
        self.pool_states: Dict[str, PoolState] = {}
        self.subscriptions = set()
        self.last_update = {}
        self.active_pools: Set[str] = set()
        self.logger = logger
        self.logger.info("RaydiumMonitor initialized with Helius WebSocket: %s", helius_ws)

    async def subscribe_pool(self, pool_address: str):
        """Subscribe to pool account updates."""
        try:
            async with websockets.connect(self.helius_ws) as ws:
                logger.debug("Subscribing to pool address: %s", pool_address)
                
                # Subscribe to account updates
                account_sub = {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "accountSubscribe",
                    "params": [
                        pool_address,
                        {
                            "encoding": "jsonParsed",
                            "commitment": "confirmed"
                        }
                    ]
                }
                
                # Subscribe to token metadata
                token_sub = {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tokenMetadataSubscribe",
                    "params": [{"mint": pool_address}]
                }
                
                await ws.send(json.dumps(account_sub))
                response = await ws.recv()
                subscription_id = json.loads(response)['result']
                self.subscriptions.add(subscription_id)
                
                await ws.send(json.dumps(token_sub))
                logger.info("Subscribed to pool %s", pool_address)
                
                while True:
                    msg = await ws.recv()
                    data = json.loads(msg)
                    
                    if 'params' in data:
                        if 'result' in data['params']:
                            await self.process_pool_update(data['params'])
                        elif 'subscription' in data['params']:
                            await self.process_token_update(data['params'])
                            
        except Exception as e:
            logger.error("Subscription error for pool %s: %s", pool_address, e)
            await asyncio.sleep(1)
            await self.subscribe_pool(pool_address)

    async def process_pool_update(self, update_data: dict):
        """Process pool account updates."""
        logger.debug("Processing pool update")
        try:
            account = update_data.get('result', {}).get('value', {})
            if not account:
                logger.warning("No account data found in update.")
                return

            pool_data = account.get('data')
            if not pool_data or len(pool_data) < 32:
                logger.warning("Insufficient data length for pool: %s", account.get('pubkey'))
                return

            pool_address = account.get('pubkey')
            if not pool_address:
                logger.warning("No pool address found in update.")
                return

            # Parse pool reserves
            base_reserve = int.from_bytes(pool_data[8:16], 'little')
            quote_reserve = int.from_bytes(pool_data[16:24], 'little')
            token_mint = pool_data[64:96].hex()  # Extract token mint
            
            # Convert to Decimal
            LAMPORTS_PER_SOL = Decimal('1000000000')
            base_reserve_sol = Decimal(str(base_reserve)) / LAMPORTS_PER_SOL
            quote_reserve_sol = Decimal(str(quote_reserve)) / LAMPORTS_PER_SOL
            
            # Calculate liquidity in SOL terms
            liquidity = float(base_reserve_sol)
            
            # Update pool state
            self.pool_states[pool_address] = PoolState(
                base_reserve=base_reserve_sol,
                quote_reserve=quote_reserve_sol,
                timestamp=time.time(),
                token_mint=token_mint,
                liquidity=liquidity
            )
            
            self.last_update[pool_address] = time.time()
            self.active_pools.add(pool_address)
            
            logger.info(f"""
            Pool Update: {pool_address}
            Base Reserve: {base_reserve_sol:.6f} SOL
            Quote Reserve: {quote_reserve_sol:.6f} SOL
            Token Mint: {token_mint}
            Liquidity: {liquidity:.2f} SOL
            """)

        except Exception as e:
            logger.error("Update processing error: %s", e, exc_info=True)

    async def process_token_update(self, update_data: dict):
        """Process token metadata updates."""
        logger.debug("Processing token update")
        try:
            token_data = update_data.get('result', {})
            if not token_data:
                logger.warning("No token data found in update.")
                return

            mint = token_data.get('mint')
            price = token_data.get('price')
            
            if mint and price:
                # Update price for all pools with this token
                for pool_state in self.pool_states.values():
                    if pool_state.token_mint == mint:
                        pool_state.price_usd = price
                        logger.info(f"Updated price for {mint}: ${price:.4f}")
                        
        except Exception as e:
            logger.error("Token update error: %s", e)

    def get_pool_state(self, pool_address: str) -> Optional[PoolState]:
        """Get latest state for a pool."""
        logger.debug("Getting pool state for %s", pool_address)
        return self.pool_states.get(pool_address)

    async def monitor_logs(self):
        """Monitor Raydium program logs."""
        logger.info("Starting to monitor Raydium program logs")
        try:
            async with websockets.connect(self.helius_ws) as ws:
                logs_sub = {
                    "jsonrpc": "2.0",
                    "id": "logs",
                    "method": "logsSubscribe",
                    "params": [
                        {"mentions": ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"]},
                        {"commitment": "confirmed"}
                    ]
                }
                
                await ws.send(json.dumps(logs_sub))
                logger.info("Subscribed to Raydium program logs")
                
                while True:
                    msg = await ws.recv()
                    data = json.loads(msg)
                    
                    if 'params' in data and 'result' in data['params']:
                        await self.process_log(data['params']['result'])
            logger.info("Finished monitoring logs")

        except Exception as e:
            logger.error("Log monitoring error: %s", e)
            await asyncio.sleep(1)
            await self.monitor_logs()

    async def process_log(self, log_data: dict):
        """Process Raydium program log."""
        logger.debug("Processing log data")
        try:
            logs = log_data.get('value', {}).get('logs', [])
            for log in logs:
                if "ray_log:" in log:
                    b64_data = log.split('ray_log: ')[1].strip()
                    raw_data = base64.b64decode(b64_data)
                    await self.analyze_trade(raw_data)

        except Exception as e:
            logger.error("Log processing error: %s", e)

    async def analyze_trade(self, data: bytes):
        """Analyze trade data."""
        logger.debug("Analyzing trade data")
        try:
            amount_in = int.from_bytes(data[0:8], 'little')
            amount_out = int.from_bytes(data[8:16], 'little')
            token_mint = data[64:96].hex()

            # Convert to SOL
            LAMPORTS_PER_SOL = 1_000_000_000
            amount_in_sol = amount_in / LAMPORTS_PER_SOL
            amount_out_sol = amount_out / LAMPORTS_PER_SOL

            logger.info(f"""
            Trade detected:
            Amount In: {amount_in_sol:.6f} SOL
            Amount Out: {amount_out_sol:.6f} SOL
            Token: {token_mint}
            """)
            
        except Exception as e:
            logger.error("Trade analysis error: %s", e)

    async def start_monitoring(self, pool_addresses: list):
        """Start monitoring multiple pools."""
        logger.info("Starting to monitor pools: %s", pool_addresses)
        tasks = [
            self.subscribe_pool(addr) for addr in pool_addresses
        ]
        tasks.append(self.monitor_logs())

        await asyncio.gather(*tasks)

    async def stop_monitoring(self):
        """Stop all monitoring."""
        self.subscriptions.clear()
        self.pool_states.clear()
        self.active_pools.clear()
        logger.info("Stopped monitoring all pools.") please fix spacing and add logging only printing in terminal logging please thank you