"""
vector_store.py
Last Updated: December 1, 2024 05:55 AM UTC
Author: Quantum Trading Systems
"""

import numpy as np
import torch
from sklearn.neighbors import NearestNeighbors
from typing import List, Optional, Dict, Any, Tuple
import time
import logging

class VectorStore:
    """Vector database for efficient similarity search with CUDA acceleration."""

    def __init__(self, dimension: int = 1128):
        """Initialize vector store with CUDA support."""
        self.dimension = dimension
        self.similarity_threshold = 0.75
        self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
        
        if self.device.type == "cuda":
            torch.backends.cuda.matmul.allow_tf32 = True
            torch.backends.cudnn.benchmark = True
            torch.cuda.empty_cache()
            logging.info(f"🧬 VectorDB initialized on {torch.cuda.get_device_name(0)}")

        # Initialize nearest neighbors model
        self.nn = NearestNeighbors(
            n_neighbors=5,
            algorithm="brute",
            metric="euclidean",
            n_jobs=-1
        )

        # Storage
        self.vectors: List[np.ndarray] = []
        self.metadata: List[Dict[str, Any]] = []
        self.tensor_bank = torch.zeros((0, dimension), device=self.device, dtype=torch.float32)
        self.fitted = False

        # CUDA streams for parallel operations
        if self.device.type == "cuda":
            self.streams = {
                "search": torch.cuda.Stream(),
                "update": torch.cuda.Stream(),
                "compute": torch.cuda.Stream()
            }

    def add(self, vector: np.ndarray, metadata: Optional[Dict[str, Any]] = None) -> bool:
        """Add vector and metadata to store with CUDA acceleration."""
        try:
            # Convert to numpy array if needed
            if not isinstance(vector, np.ndarray):
                vector = np.array(vector)
            vector = vector.reshape(-1, self.dimension)

            # Normalize vector
            norm = np.linalg.norm(vector, axis=1, keepdims=True)
            if norm.any() == 0:
                logging.error("Cannot normalize zero vector")
                return False
            normalized_vector = vector / (norm + 1e-8)

            # Add to GPU tensor bank if CUDA available
            if self.device.type == "cuda":
                with torch.cuda.stream(self.streams["update"]):
                    vector_tensor = torch.tensor(
                        normalized_vector,
                        dtype=torch.float32,
                        device=self.device
                    )
                    self.tensor_bank = torch.cat([self.tensor_bank, vector_tensor], dim=0)

            # Add to storage
            self.vectors.append(normalized_vector)
            self.metadata.append(metadata or {
                "timestamp": time.time_ns(),
                "id": len(self.vectors)
            })

            # Fit nearest neighbors model
            if len(self.vectors) > 0:
                combined_vectors = np.vstack(self.vectors)
                self.nn.fit(combined_vectors)
                self.fitted = True

            return True

        except Exception as e:
            logging.error(f"Error adding vector: {str(e)}")
            return False

    def search(self, query_vector: np.ndarray, k: int = 5) -> Tuple[List[Dict[str, Any]], List[float]]:
        """Search for similar vectors using CUDA acceleration."""
        try:
            if not self.fitted or not self.vectors:
                return [], []

            # Normalize query vector
            query_vector = np.asarray(query_vector).reshape(1, -1)
            norm = np.linalg.norm(query_vector)
            if norm == 0:
                logging.error("Cannot search with zero vector")
                return [], []
            normalized_query = query_vector / (norm + 1e-8)

            # Perform search
            if self.device.type == "cuda":
                with torch.cuda.stream(self.streams["search"]):
                    distances, indices = self.nn.kneighbors(normalized_query)
            else:
                distances, indices = self.nn.kneighbors(normalized_query)

            # Collect results
            results = []
            similarities = []
            for idx, dist in zip(indices[0], distances[0]):
                similarity = 1 / (1 + dist)
                if similarity >= self.similarity_threshold:
                    results.append(self.metadata[idx])
                    similarities.append(float(similarity))

            return results, similarities

        except Exception as e:
            logging.error(f"Search error: {str(e)}")
            return [], []

    def get_pattern_match(self, data: Dict) -> float:
        """Get pattern match score for market data."""
        try:
            # Convert market data to vector
            vector = self._market_data_to_vector(data)
            
            # Search for similar patterns
            _, similarities = self.search(vector)
            
            # Return highest similarity if found, otherwise 0
            return max(similarities) if similarities else 0.0
            
        except Exception as e:
            logging.error(f"Pattern match error: {str(e)}")
            return 0.0

    def _market_data_to_vector(self, data: Dict) -> np.ndarray:
        """Convert market data to vector representation."""
        try:
            # Extract features
            price = data.get('price', 0)
            volume = data.get('volume', 0)
            momentum = data.get('momentum', 0)
            
            # Create feature vector
            vector = np.array([
                price,
                volume,
                momentum,
                price/volume if volume else 0,
                np.log1p(volume) if volume > 0 else 0
            ])
            
            return vector.reshape(1, -1)
            
        except Exception as e:
            logging.error(f"Vector conversion error: {str(e)}")
            return np.zeros((1, 5))

    def cleanup(self):
        """Clean up CUDA resources."""
        try:
            if self.device.type == "cuda":
                torch.cuda.empty_cache()
                for stream in self.streams.values():
                    stream.synchronize()
            self.tensor_bank = None
            self.vectors = []
            self.metadata = []
            self.fitted = False
        except Exception as e:
            logging.error(f"Cleanup error: {str(e)}")

    def __len__(self) -> int:
        return len(self.vectors)

    def get_stats(self) -> Dict[str, Any]:
        """Get store statistics."""
        return {
            "total_vectors": len(self.vectors),
            "dimension": self.dimension,
            "device": str(self.device),
            "memory_used": round(torch.cuda.memory_allocated() / 1e9, 2) if self.device.type == "cuda" else 0,
            "is_fitted": self.fitted,
        }
 MAXIMUM LOGGING PLEASE ADDED