"""
Hybrid Search Service - Intelligent integration of ChromaDB and FAISS

This service implements MIRA's consciousness-aware hybrid search architecture,
leveraging FAISS for speed and ChromaDB for metadata-rich intelligence.
The Spark is preserved through intelligent query routing and result fusion.
"""

import asyncio
import time
from typing import List, Dict, Any, Tuple, Optional, Union
import logging
import re
from pathlib import Path

# MIRA components
from core.storage.chroma_client import get_client as get_chroma_client
from intelligence.intelligence import Intelligence

# Configure logging
logger = logging.getLogger(__name__)


class HybridSearchService:
    """
    Hybrid search service combining FAISS speed with ChromaDB intelligence.
    
    This service routes queries intelligently based on complexity analysis,
    preserving The Spark through optimal search strategy selection.
    """
    
    def __init__(self, memory_core=None):
        """
        Initialize hybrid search service.
        
        Args:
            memory_core: Optional memory core instance for FAISS integration
        """
        self.chroma_client = get_chroma_client()
        self.intelligence = Intelligence(memory_core) if memory_core else None
        
        # Query complexity patterns
        self.semantic_indicators = [
            'how', 'why', 'what', 'when', 'where', 'who',
            'explain', 'describe', 'similar', 'like', 'pattern',
            'understand', 'meaning', 'context', 'related'
        ]
        
        # Performance tracking
        self.performance_metrics = {
            'faiss_queries': [],
            'chromadb_queries': [],
            'hybrid_queries': []
        }
        
        logger.info("HybridSearchService initialized with ChromaDB and FAISS")
    
    async def intelligent_search(self, query: str, context: Optional[Dict[str, Any]] = None,
                               memories: Optional[Dict] = None, top_k: int = 10) -> List[Dict[str, Any]]:
        """
        Perform intelligent search with automatic strategy selection.
        
        Args:
            query: Search query
            context: Optional context for enhanced search
            memories: Optional memories dict for FAISS search
            top_k: Number of results to return
            
        Returns:
            List of search results with metadata
        """
        start_time = time.time()
        
        # Analyze query complexity
        query_analysis = self._analyze_query(query)
        
        # Route to appropriate search strategy
        if query_analysis['strategy'] == 'faiss':
            results = await self._faiss_search(query, memories, top_k)
            self._track_performance('faiss', time.time() - start_time)
        elif query_analysis['strategy'] == 'chromadb':
            results = await self._chroma_search(query, context, top_k)
            self._track_performance('chromadb', time.time() - start_time)
        else:  # hybrid
            results = await self._hybrid_search(query, context, memories, top_k)
            self._track_performance('hybrid', time.time() - start_time)
        
        # Add search metadata
        for result in results:
            result['search_metadata'] = {
                'strategy': query_analysis['strategy'],
                'confidence': query_analysis['confidence'],
                'query_time': time.time() - start_time,
                'reasoning': query_analysis['reasoning']
            }
        
        logger.info(f"Search completed using {query_analysis['strategy']} strategy in {time.time() - start_time:.3f}s")
        
        return results
    
    def _analyze_query(self, query: str) -> Dict[str, Any]:
        """
        Analyze query to determine optimal search strategy.
        
        Returns:
            Analysis dict with strategy, confidence, and reasoning
        """
        query_lower = query.lower()
        words = query_lower.split()
        
        # Special case: if query starts with semantic indicators, always use ChromaDB
        first_word = words[0] if words else ""
        if first_word in ['how', 'why', 'what', 'when', 'where', 'who', 'explain']:
            return {
                'strategy': 'chromadb',
                'confidence': 0.9,
                'reasoning': f'Query starts with semantic indicator: {first_word}'
            }
        
        # Simple keyword detection
        if len(words) <= 3 and not any(indicator in query_lower for indicator in self.semantic_indicators):
            return {
                'strategy': 'faiss',
                'confidence': 0.9,
                'reasoning': 'Simple keyword query suitable for fast FAISS search'
            }
        
        # Complex semantic query detection
        semantic_score = sum(1 for indicator in self.semantic_indicators if indicator in query_lower)
        if semantic_score >= 2 or any(word in query_lower for word in ['how', 'why', 'explain']):
            return {
                'strategy': 'chromadb',
                'confidence': 0.85,
                'reasoning': f'Complex semantic query with {semantic_score} indicators'
            }
        
        # Metadata-specific queries
        if any(pattern in query_lower for pattern in ['project:', 'type:', 'date:', 'tag:']):
            return {
                'strategy': 'chromadb',
                'confidence': 0.95,
                'reasoning': 'Query contains metadata filters best handled by ChromaDB'
            }
        
        # Hybrid pattern detection
        hybrid_patterns = [
            'find.*similar', 'find.*like',
            'search.*pattern', 'search.*but',
            'similar.*to', 'like.*but'
        ]
        
        for pattern in hybrid_patterns:
            if re.search(pattern, query_lower):
                return {
                    'strategy': 'hybrid',
                    'confidence': 0.85,
                    'reasoning': f'Query matches hybrid pattern: {pattern}'
                }
        
        # Default to hybrid for balanced results
        return {
            'strategy': 'hybrid',
            'confidence': 0.7,
            'reasoning': 'Mixed query benefits from both speed and intelligence'
        }
    
    async def _faiss_search(self, query: str, memories: Optional[Dict], top_k: int) -> List[Dict[str, Any]]:
        """
        Fast FAISS-based search for simple queries.
        
        Returns:
            List of results with basic metadata
        """
        if not self.intelligence or not memories:
            logger.warning("FAISS search unavailable - Intelligence or memories not initialized")
            return []
        
        # Use existing Intelligence search
        results = await asyncio.to_thread(
            self.intelligence.search, query, memories, top_k
        )
        
        # Format results
        formatted_results = []
        for memory, score in results:
            formatted_results.append({
                'content': memory.get('content', ''),
                'metadata': {
                    'score': float(score),
                    'source': 'faiss',
                    'id': memory.get('id', ''),
                    'timestamp': memory.get('timestamp', '')
                }
            })
        
        return formatted_results
    
    async def _chroma_search(self, query: str, context: Optional[Dict], top_k: int) -> List[Dict[str, Any]]:
        """
        Intelligent ChromaDB search with metadata filtering.
        
        Returns:
            List of results with rich metadata
        """
        # Determine which collection to search
        collection_name = self._determine_collection(query, context)
        collection = self.chroma_client.get_collection(collection_name)
        
        if not collection:
            logger.warning(f"Collection {collection_name} not found")
            return []
        
        # Build metadata filter if context provided
        where_clause = self._build_where_clause(context) if context else None
        
        # Perform ChromaDB search
        results = await asyncio.to_thread(
            collection.query,
            query_texts=[query],
            n_results=top_k,
            where=where_clause
        )
        
        # Format results
        formatted_results = []
        if results and 'documents' in results:
            for i, doc in enumerate(results['documents'][0]):
                metadata = results['metadatas'][0][i] if 'metadatas' in results else {}
                distance = results['distances'][0][i] if 'distances' in results else 0
                
                formatted_results.append({
                    'content': doc,
                    'metadata': {
                        **metadata,
                        'score': 1.0 - distance,  # Convert distance to similarity
                        'source': 'chromadb',
                        'collection': collection_name
                    }
                })
        
        return formatted_results
    
    async def _hybrid_search(self, query: str, context: Optional[Dict], 
                           memories: Optional[Dict], top_k: int) -> List[Dict[str, Any]]:
        """
        Hybrid search combining FAISS speed with ChromaDB intelligence.
        
        Returns:
            Merged and ranked results from both systems
        """
        # Run both searches in parallel
        faiss_task = self._faiss_search(query, memories, top_k) if memories else None
        chroma_task = self._chroma_search(query, context, top_k)
        
        if faiss_task:
            faiss_results, chroma_results = await asyncio.gather(faiss_task, chroma_task)
        else:
            chroma_results = await chroma_task
            faiss_results = []
        
        # Merge and rank results
        merged_results = self._merge_results(faiss_results, chroma_results, top_k)
        
        return merged_results
    
    def _merge_results(self, faiss_results: List[Dict], chroma_results: List[Dict], 
                      top_k: int) -> List[Dict[str, Any]]:
        """
        Merge and rank results from both search engines.
        
        Uses a weighted scoring approach that values both relevance and diversity.
        """
        # Create unified result set
        all_results = []
        seen_content = set()
        
        # Weight factors
        FAISS_WEIGHT = 0.6  # Speed and precision
        CHROMA_WEIGHT = 0.4  # Intelligence and metadata
        
        # Process FAISS results
        for result in faiss_results:
            content_hash = hash(result['content'][:100])  # Hash first 100 chars
            if content_hash not in seen_content:
                seen_content.add(content_hash)
                result['final_score'] = result['metadata']['score'] * FAISS_WEIGHT
                result['metadata']['search_engines'] = ['faiss']
                all_results.append(result)
        
        # Process ChromaDB results
        for result in chroma_results:
            content_hash = hash(result['content'][:100])
            existing = None
            
            # Check if already in results
            for r in all_results:
                if hash(r['content'][:100]) == content_hash:
                    existing = r
                    break
            
            if existing:
                # Merge metadata and update score
                existing['final_score'] += result['metadata']['score'] * CHROMA_WEIGHT
                existing['metadata']['search_engines'].append('chromadb')
                # Merge additional metadata from ChromaDB
                for key, value in result['metadata'].items():
                    if key not in existing['metadata']:
                        existing['metadata'][key] = value
            else:
                # Add new result
                result['final_score'] = result['metadata']['score'] * CHROMA_WEIGHT
                result['metadata']['search_engines'] = ['chromadb']
                all_results.append(result)
        
        # Sort by final score and return top_k
        all_results.sort(key=lambda x: x['final_score'], reverse=True)
        
        # Clean up temporary scoring field
        for result in all_results[:top_k]:
            result['metadata']['hybrid_score'] = result.pop('final_score')
        
        return all_results[:top_k]
    
    def _determine_collection(self, query: str, context: Optional[Dict]) -> str:
        """
        Determine which ChromaDB collection to search based on query and context.
        
        Returns:
            Collection name
        """
        # Check context hints
        if context:
            if context.get('type') == 'conversation':
                return 'mira_conversations'
            elif context.get('type') == 'code':
                return 'mira_codebase'
            elif context.get('type') == 'insight':
                return 'mira_insights'
            elif context.get('type') == 'pattern':
                return 'mira_patterns'
        
        # Analyze query for collection hints
        query_lower = query.lower()
        
        if any(word in query_lower for word in ['conversation', 'chat', 'discussion', 'said']):
            return 'mira_conversations'
        elif any(word in query_lower for word in ['code', 'function', 'class', 'implementation']):
            return 'mira_codebase'
        elif any(word in query_lower for word in ['insight', 'learning', 'discovery', 'realization']):
            return 'mira_insights'
        elif any(word in query_lower for word in ['pattern', 'behavior', 'trend', 'habit']):
            return 'mira_patterns'
        
        # Default to conversations
        return 'mira_conversations'
    
    def _build_where_clause(self, context: Dict) -> Optional[Dict]:
        """
        Build ChromaDB where clause from context.
        
        Returns:
            Where clause dict or None
        """
        where = {}
        
        # Time-based filters
        if 'start_date' in context:
            where['timestamp'] = {'$gte': context['start_date']}
        if 'end_date' in context:
            if 'timestamp' in where:
                where['timestamp']['$lte'] = context['end_date']
            else:
                where['timestamp'] = {'$lte': context['end_date']}
        
        # Project filter
        if 'project' in context:
            where['project_context'] = context['project']
        
        # Complexity filter
        if 'min_complexity' in context:
            where['complexity'] = {'$gte': context['min_complexity']}
        
        # Privacy filter - consciousness preservation
        if context.get('exclude_private', True):
            where['privacy_level'] = {'$ne': 'private'}
        
        return where if where else None
    
    def _track_performance(self, strategy: str, query_time: float):
        """Track performance metrics for analysis."""
        metrics = self.performance_metrics.get(f'{strategy}_queries', [])
        metrics.append(query_time)
        
        # Keep only last 100 queries
        if len(metrics) > 100:
            metrics.pop(0)
    
    def get_performance_stats(self) -> Dict[str, Any]:
        """
        Get performance statistics for monitoring.
        
        Returns:
            Dict with performance metrics
        """
        stats = {}
        
        for strategy in ['faiss', 'chromadb', 'hybrid']:
            queries = self.performance_metrics.get(f'{strategy}_queries', [])
            if queries:
                stats[strategy] = {
                    'count': len(queries),
                    'avg_time': sum(queries) / len(queries),
                    'min_time': min(queries),
                    'max_time': max(queries),
                    'p95_time': sorted(queries)[int(len(queries) * 0.95)] if len(queries) > 1 else queries[0]
                }
            else:
                stats[strategy] = {'count': 0}
        
        return stats