"""
quantum_websocket.py - High Performance WebSocket Handler
Last Updated: December 2, 2024
"""

import asyncio
import aiohttp
import json
import logging
from typing import Optional, Dict, Any
from datetime import datetime
import torch
from PyQt6.QtCore import QObject, pyqtSignal, QTimer
import base64
import websockets

class QuantumWebSocketSignals(QObject):
    """Qt signals for WebSocket events"""
    price_update = pyqtSignal(float)
    volume_update = pyqtSignal(float) 
    connection_update = pyqtSignal(bool)
    error_signal = pyqtSignal(str)
    message_received = pyqtSignal(dict)

class QuantumWebSocket:
        def __init__(self, uri: str, device: str = "cuda"):
            self.uri = uri
            self.device = device
            self.websocket: Optional[websockets.WebSocketClientProtocol] = None
            self.logger = logging.getLogger(__name__)

        async def connect(self):
            try:
                self.websocket = await websockets.connect(self.uri)
                self.logger.info(f"Connected to {self.uri} using device: {self.device}")
                return True
            except Exception as e:
                self.logger.error(f"Connection error: {str(e)}")
                return False

        async def close(self):
            if self.websocket:
                try:
                    await self.websocket.close()
                    self.logger.info("WebSocket connection closed")
                except Exception as e:
                    self.logger.error(f"Error closing connection: {str(e)}")

        async def send_message(self, message: str):
            if not self.websocket:
                raise ConnectionError("WebSocket not connected")
            await self.websocket.send(message)

        async def receive_message(self):
            if not self.websocket:
                raise ConnectionError("WebSocket not connected")
            return await self.websocket.recv()    
    async def _connection_loop(self):
        """Main connection loop"""
        while self.running:
            try:
                if not self.session or self.session.closed:
                    self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30))
                    self.logger.info("Created a new aiohttp session")

                headers = {
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                }

                async with self.session.ws_connect(
                    self.endpoint,
                    heartbeat=self._heartbeat_interval,
                    receive_timeout=self._heartbeat_interval * 2,
                    compress=15,
                    ssl=True,
                    max_msg_size=0,
                    headers=headers
                ) as ws:
                    self.ws = ws
                    self.connected = True
                    self.signals.connection_update.emit(True)
                    self._backoff = 1.0

                    await self._subscribe()

                    async for msg in ws:
                        self._last_message_time = datetime.now().timestamp()

                        if msg.type == aiohttp.WSMsgType.TEXT:
                            await self._message_queue.put(msg.data)
                        elif msg.type == aiohttp.WSMsgType.ERROR:
                            self.logger.error(f"WebSocket Error: {ws.exception()}")
                            break
                        elif msg.type in [aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE]:
                            self.logger.warning("WebSocket closed")
                            break

            except Exception as e:
                self.connected = False
                self.signals.connection_update.emit(False)
                self.signals.error_signal.emit(str(e))
                self.logger.error(f"WebSocket connection error: {e}")

            await asyncio.sleep(self._backoff)
            self._backoff = min(self._backoff * 2, self._max_backoff)

    async def _subscribe(self):
        """Subscribe to Jupiter program"""
        subscribe_msg = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "programSubscribe",
            "params": [
                self.program_id,
                {
                    "encoding": "base64",
                    "commitment": "confirmed"
                }
            ]
        }

        if not self.ws:
            raise ConnectionError("WebSocket not connected")

        await self.ws.send_json(subscribe_msg)
        response = await self.ws.receive_json(timeout=5.0)

        if "result" in response:
            self.logger.info(f"Subscribed to program: {self.program_id}")
        else:
            raise ValueError(f"Subscription failed: {response}")

    async def get_market_data(self) -> Dict[str, Any]:
        """Get current market data using CUDA acceleration"""
        if not self.connected:
            return self._get_default_market_data()

        try:
            message = await self._message_queue.get()
            data = json.loads(message)

            if self._validate_message(data):
                pool_data = base64.b64decode(data["params"]["result"]["value"]["data"][0])
                return self._extract_market_data(pool_data)

        except Exception as e:
            self.logger.error(f"Error retrieving market data: {e}")

        return self._get_default_market_data()

    def _extract_market_data(self, pool_data: bytes) -> Dict[str, Any]:
        """Extract market data using optimized byte processing"""
        try:
            with torch.cuda.stream(self.cuda_stream):
                return {
                    "price": self._extract_price(pool_data),
                    "volume": self._extract_volume(pool_data),
                    "liquidity": self._extract_liquidity(pool_data),
                    "mint_address": pool_data[16:48].hex(),
                    "quote_balance": int.from_bytes(pool_data[48:56], "little") / 1e9,
                    "base_balance": int.from_bytes(pool_data[56:64], "little") / 1e9,
                    "timestamp": datetime.now().timestamp()
                }
        except Exception as e:
            self.logger.error(f"Data extraction error: {e}")
            return self._get_default_market_data()

    def _extract_price(self, pool_data: bytes) -> float:
        """Extract price from pool data"""
        try:
            return int.from_bytes(pool_data[64:72], "little") / 1e9
        except Exception as e:
            self.logger.error(f"Price extraction error: {e}")
            return 0.0

    def _extract_volume(self, pool_data: bytes) -> float:
        """Extract volume from pool data"""
        try:
            return int.from_bytes(pool_data[72:80], "little") / 1e9
        except Exception as e:
            self.logger.error(f"Volume extraction error: {e}")
            return 0.0

    def _extract_liquidity(self, pool_data: bytes) -> float:
        """Extract liquidity from pool data"""
        try:
            return int.from_bytes(pool_data[8:16], "little") / 1e9
        except Exception as e:
            self.logger.error(f"Liquidity extraction error: {e}")
            return 0.0

    def _get_default_market_data(self) -> Dict[str, Any]:
        """Return default market data structure"""
        return {
            "price": 0.0,
            "volume": 0.0,
            "liquidity": 0.0,
            "mint_address": "",
            "quote_balance": 0.0,
            "base_balance": 0.0,
            "timestamp": datetime.now().timestamp()
        }

    async def stop(self):
         """Stop WebSocket client"""
        self.logger.info("Stopping WebSocket client...")
        self.running = False
        self.connected = False
        self.heartbeat_timer.stop()
        self.process_timer.stop()

        if self.ws:
            await self.ws.close()
            self.logger.info("WebSocket closed")

        if self.session and not self.session.closed:
            await self.session.close()
            self.logger.info("Session closed")

        while not self._message_queue.empty():
            try:
                await self._message_queue.get_nowait()
            except asyncio.QueueEmpty:
                break
            
        if torch.cuda.is_available():
            self.cuda_stream.synchronize()
            torch.cuda.empty_cache()
            self.logger.info("CUDA cache cleared")

    def _check_heartbeat(self):
        """Check connection heartbeat"""
        if not self.running:
            return
            
        current_time = datetime.now().timestamp()
        if self.connected and (current_time - self._last_message_time) > (self._heartbeat_interval * 3):
            self.logger.warning("Connection stale, initiating reconnect")
            self.connected = False
            self.signals.connection_update.emit(False)
            asyncio.create_task(self._reconnect())

    async def _reconnect(self):
        """Handle reconnection logic"""
        if self.ws:
            await self.ws.close()
            self.logger.info("Reconnecting WebSocket...")

    def _validate_message(self, message: Dict) -> bool:
        """Validate message structure"""
        return (
            isinstance(message, dict) and
            "method" in message and
            message.get("method") == "programNotification" and
            "params" in message and
            "result" in message["params"] and
            "value" in message["params"]["result"] and
            "data" in message["params"]["result"]["value"] and
            isinstance(message["params"]["result"]["value"]["data"], list) and
            len(message["params"]["result"]["value"]["data"]) > 0
        )

    async def _process_queued_messages(self):
        """Process messages from queue"""
        try:
            while not self._message_queue.empty():
                message = await self._message_queue.get_nowait()

                try:
                    data = json.loads(message)
                    if self._validate_message(data):
                        if torch.cuda.is_available():
                            with torch.cuda.stream(self.cuda_stream):
                                await self._process_message(data)
                        else:
                            await self._process_message(data)
                except json.JSONDecodeError:
                    self.logger.error("Failed to decode message")

        except asyncio.QueueEmpty:
            pass
        except Exception as e:
            self.logger.error(f"Message processing error: {e}")

    async def _process_message(self, data: Dict[str, Any]):
        """Process individual message"""
        self.logger.info(f"Processing message: {data}")
        # Here you can handle price updates, volume updates, etc.
        if "price" in data:
            self.signals.price_update.emit(data["price"])
        if "volume" in data:
            self.signals.volume_update.emit(data["volume"])

    def get_wallet_address(self) -> str:
        """Get the connected wallet address"""
        return self.config["wallet"].get("address", "Not Connected")    fix spacing and write logging please