"""
BGX Vault module - In-memory result store with TTL and LRU eviction.

This module provides the ResultVault class that stores task results
with time-based expiration and size-based LRU eviction.
"""

import threading
import time
from collections import OrderedDict
from typing import Any, Dict, Optional


class ResultVault:
    """In-memory result store with TTL and LRU eviction."""
    
    def __init__(self, ttl: float = 300.0, max_size: int = 1000):
        """Initialize the result vault.
        
        Args:
            ttl: Time to live for results in seconds (default: 300)
            max_size: Maximum number of entries before LRU eviction (default: 1000)
        """
        self.ttl = ttl
        self.max_size = max_size
        
        # OrderedDict provides O(1) access and maintains insertion order
        # We use it as an LRU cache by moving accessed items to the end
        self._store: OrderedDict[str, Dict[str, Any]] = OrderedDict()
        self._lock = threading.RLock()
        
        # Background reaper thread for TTL cleanup
        self._reaper_thread = threading.Thread(
            target=self._reaper_worker,
            name="bgx-vault-reaper",
            daemon=True
        )
        self._shutdown_event = threading.Event()
        self._reaper_thread.start()
    
    def store(self, task_id: str, result: Any = None, error: Exception = None) -> None:
        """Store a task result or error.
        
        Args:
            task_id: Unique task identifier
            result: Task result (if successful)
            error: Task error (if failed)
        """
        current_time = time.time()
        
        with self._lock:
            # Remove existing entry if present
            if task_id in self._store:
                del self._store[task_id]
            
            # Add new entry
            self._store[task_id] = {
                "result": result,
                "error": error,
                "stored_at": current_time,
                "last_accessed": current_time
            }
            
            # Enforce size limit with LRU eviction
            while len(self._store) > self.max_size:
                # Remove oldest entry (first item in OrderedDict)
                oldest_id = next(iter(self._store))
                del self._store[oldest_id]
    
    def get(self, task_id: str) -> Optional[Any]:
        """Retrieve a task result.
        
        Args:
            task_id: Unique task identifier
            
        Returns:
            Task result if found and not expired, None otherwise
        """
        current_time = time.time()
        
        with self._lock:
            if task_id not in self._store:
                return None
            
            entry = self._store[task_id]
            
            # Check TTL
            if current_time - entry["stored_at"] > self.ttl:
                del self._store[task_id]
                return None
            
            # Update access time and move to end (LRU)
            entry["last_accessed"] = current_time
            self._store.move_to_end(task_id)
            
            # Return result or raise error
            if entry["error"] is not None:
                raise entry["error"]
            
            return entry["result"]
    
    def wait_for(self, task_id: str, timeout: Optional[float] = None) -> Any:
        """Wait for a task result to become available.
        
        Args:
            task_id: Unique task identifier
            timeout: Maximum time to wait in seconds
            
        Returns:
            Task result
            
        Raises:
            TimeoutError: If timeout is reached
            KeyError: If task_id is not found
        """
        start_time = time.time()
        check_interval = 0.01  # 10ms
        
        while True:
            # Try to get result
            try:
                result = self.get(task_id)
                if result is not None:
                    return result
            except Exception as e:
                # Re-raise stored exceptions
                raise e
            
            # Check timeout
            if timeout is not None and (time.time() - start_time) > timeout:
                raise TimeoutError(f"Result for task {task_id} not available within {timeout} seconds")
            
            # Sleep before next check
            time.sleep(check_interval)
    
    def remove(self, task_id: str) -> bool:
        """Remove a task result from the vault.
        
        Args:
            task_id: Unique task identifier
            
        Returns:
            True if entry was removed, False if not found
        """
        with self._lock:
            if task_id in self._store:
                del self._store[task_id]
                return True
            return False
    
    def clear(self) -> int:
        """Clear all entries from the vault.
        
        Returns:
            Number of entries removed
        """
        with self._lock:
            count = len(self._store)
            self._store.clear()
            return count
    
    def size(self) -> int:
        """Get current number of entries in the vault."""
        with self._lock:
            return len(self._store)
    
    def get_stats(self) -> Dict[str, Any]:
        """Get vault statistics.
        
        Returns:
            Dictionary containing vault statistics
        """
        with self._lock:
            current_time = time.time()
            expired_count = 0
            
            for entry in self._store.values():
                if current_time - entry["stored_at"] > self.ttl:
                    expired_count += 1
            
            return {
                "total_entries": len(self._store),
                "max_size": self.max_size,
                "ttl": self.ttl,
                "expired_entries": expired_count,
                "utilization": len(self._store) / self.max_size
            }
    
    def get_entry_info(self, task_id: str) -> Optional[Dict[str, Any]]:
        """Get detailed information about a vault entry.
        
        Args:
            task_id: Unique task identifier
            
        Returns:
            Entry information or None if not found
        """
        with self._lock:
            if task_id not in self._store:
                return None
            
            entry = self._store[task_id]
            current_time = time.time()
            
            return {
                "task_id": task_id,
                "has_result": entry["result"] is not None,
                "has_error": entry["error"] is not None,
                "stored_at": entry["stored_at"],
                "last_accessed": entry["last_accessed"],
                "age_seconds": current_time - entry["stored_at"],
                "time_since_access": current_time - entry["last_accessed"],
                "expired": current_time - entry["stored_at"] > self.ttl
            }
    
    def _reaper_worker(self) -> None:
        """Background worker that cleans up expired entries."""
        while not self._shutdown_event.is_set():
            try:
                current_time = time.time()
                expired_ids = []
                
                with self._lock:
                    # Find expired entries
                    for task_id, entry in self._store.items():
                        if current_time - entry["stored_at"] > self.ttl:
                            expired_ids.append(task_id)
                    
                    # Remove expired entries
                    for task_id in expired_ids:
                        del self._store[task_id]
                
                # Sleep before next cleanup cycle
                time.sleep(30.0)  # Clean up every 30 seconds
                
            except Exception as e:
                print(f"Vault reaper error: {e}")
                time.sleep(5.0)  # Brief pause on error
    
    def shutdown(self) -> None:
        """Shutdown the vault and cleanup resources."""
        self._shutdown_event.set()
        self._reaper_thread.join(timeout=5.0)
        
        # Clear all entries
        with self._lock:
            self._store.clear()


# Utility functions for common vault operations
def create_vault(ttl: float = 300.0, max_size: int = 1000) -> ResultVault:
    """Create a new ResultVault instance.
    
    Args:
        ttl: Time to live for results in seconds
        max_size: Maximum number of entries
        
    Returns:
        ResultVault instance
    """
    return ResultVault(ttl=ttl, max_size=max_size)


def short_lived_vault(ttl: float = 60.0, max_size: int = 500) -> ResultVault:
    """Create a vault for short-lived results.
    
    Args:
        ttl: Time to live in seconds (default: 1 minute)
        max_size: Maximum number of entries (default: 500)
        
    Returns:
        ResultVault instance configured for short-lived results
    """
    return ResultVault(ttl=ttl, max_size=max_size)


def long_lived_vault(ttl: float = 3600.0, max_size: int = 2000) -> ResultVault:
    """Create a vault for long-lived results.
    
    Args:
        ttl: Time to live in seconds (default: 1 hour)
        max_size: Maximum number of entries (default: 2000)
        
    Returns:
        ResultVault instance configured for long-lived results
    """
    return ResultVault(ttl=ttl, max_size=max_size)
