import logging
import os
from typing import Dict, List, Any, Optional

from local_deep_research.web_search_engines.search_engine_base import BaseSearchEngine
from local_deep_research.web_search_engines.search_engines_config import SEARCH_ENGINES
from local_deep_research.web_search_engines.search_engine_factory import create_search_engine
from local_deep_research.web_search_engines.engines.search_engine_wikipedia import WikipediaSearchEngine
from local_deep_research import config

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class MetaSearchEngine(BaseSearchEngine):
    """
    LLM-powered meta search engine that intelligently selects and uses
    the appropriate search engines based on query analysis
    """
    
    def __init__(self, 
                 llm,
                 max_results: int = 10,
                 use_api_key_services: bool = True,
                 max_engines_to_try: int = 3,
                 max_filtered_results: Optional[int] = None,
                 **kwargs):
        """
        Initialize the meta search engine.
        
        Args:
            llm: Language model instance for query classification and relevance filtering
            max_results: Maximum number of search results to return
            use_api_key_services: Whether to include services that require API keys
            max_engines_to_try: Maximum number of engines to try before giving up
            max_filtered_results: Maximum number of results to keep after filtering
            **kwargs: Additional parameters (ignored but accepted for compatibility)
        """
        # Initialize the BaseSearchEngine with the LLM and max_filtered_results
        super().__init__(llm=llm, max_filtered_results=max_filtered_results)
        
        self.max_results = max_results
        self.use_api_key_services = use_api_key_services
        self.max_engines_to_try = max_engines_to_try
        
        # Cache for engine instances
        self.engine_cache = {}
        
        # Get available engines (excluding 'meta' and 'auto')
        self.available_engines = self._get_available_engines()
        logger.info(f"Meta Search Engine initialized with {len(self.available_engines)} available engines: {', '.join(self.available_engines)}")
        
        # Create a fallback engine in case everything else fails
        self.fallback_engine = WikipediaSearchEngine(
            max_results=max_results, 
            llm=llm,
            max_filtered_results=max_filtered_results
        )
    
    def _get_available_engines(self) -> List[str]:
        """Get list of available engines, excluding 'meta' and 'auto'"""
        # Filter out 'meta' and 'auto' and check API key availability
        available = []
        for name, config in SEARCH_ENGINES.items():
            if name in ["meta", "auto"]:
                continue
                
            if config.get("requires_api_key", False) and not self.use_api_key_services:
                continue
                
            if config.get("requires_api_key", False):
                api_key_env = config.get("api_key_env")
                api_key = os.getenv(api_key_env) if api_key_env else None
                if not api_key:
                    continue
                    
            available.append(name)
        
        # Make sure we have at least one engine available
        if not available and "wikipedia" in SEARCH_ENGINES:
            available.append("wikipedia")
            
        return available
    
    def analyze_query(self, query: str) -> List[str]:
        """
        Use the LLM to analyze the query and return a ranked list of
        recommended search engines to try
        """
        if not self.available_engines:
            logger.warning("No search engines available")
            return []
        engine_descriptions = []
        for name in self.available_engines:
            logger.info(f"Processing search engine: {name}")
            try:
                description = f"- {name.upper()}: Good for {', '.join(SEARCH_ENGINES[name]['strengths'][:3])}. " \
                            f"Weaknesses: {', '.join(SEARCH_ENGINES[name]['weaknesses'][:2])}. " \
                            f"Reliability: {SEARCH_ENGINES[name]['reliability']*100:.0f}%"
                engine_descriptions.append(description)
            except KeyError as e:
                logger.error(f"Missing key for engine {name}: {e}")
                # Add a basic description for engines with missing configuration
                engine_descriptions.append(f"- {name.upper()}: General purpose search engine.")
            except Exception as e:
                logger.error(f"Error processing engine {name}: {e}")
                engine_descriptions.append(f"- {name.upper()}: General purpose search engine.")

        engine_descriptions = "\n".join(engine_descriptions)
        
        prompt = f"""Analyze this search query and rank the available search engines in order of most to least appropriate for answering it.
        
Query: "{query}"

Available search engines:
{engine_descriptions}

Consider:
1. The nature of the query (factual, academic, product-related, news, etc.)
2. The strengths and weaknesses of each engine
3. The reliability of each engine

Return ONLY a comma-separated list of search engine names in your recommended order. Example: "wikipedia,arxiv,duckduckgo"
Do not include any engines that are not listed above. Only return the comma-separated list, nothing else."""

        # Get response from LLM
        try:
            response = self.llm.invoke(prompt)
            content = response.content.strip()
            
            # Parse the response into a list of engine names
            engine_names = [name.strip().lower() for name in content.split(',')]
            
            # Filter out any invalid engine names
            valid_engines = [name for name in engine_names if name in self.available_engines]
            
            # If no valid engines were returned, use default order based on reliability
            if not valid_engines:
                valid_engines = sorted(
                    self.available_engines, 
                    key=lambda x: SEARCH_ENGINES[x]["reliability"],
                    reverse=True
                )
            
            return valid_engines
        except Exception as e:
            logger.error(f"Error analyzing query with LLM: {str(e)}")
            # Fall back to reliability-based ordering
            return sorted(
                self.available_engines, 
                key=lambda x: SEARCH_ENGINES[x]["reliability"],
                reverse=True
            )
    
    def _get_previews(self, query: str) -> List[Dict[str, Any]]:
        """
        Get preview information by selecting the best search engine for this query.
        
        Args:
            query: The search query
            
        Returns:
            List of preview dictionaries
        """
        # Get ranked list of engines for this query
        ranked_engines = self.analyze_query(query)
        
        if not ranked_engines:
            logger.warning("No suitable search engines found for query, using fallback engine")
            return self.fallback_engine._get_previews(query)
        
        # Limit the number of engines to try
        engines_to_try = ranked_engines[:self.max_engines_to_try]
        
        logger.info(f"Search plan created. Will try these engines in order: {', '.join(engines_to_try)}")
        
        all_errors = []
        # Try each engine in order
        for engine_name in engines_to_try:
            logger.info(f"Trying search engine: {engine_name}")
            
            # Get or create the engine instance
            engine = self._get_engine_instance(engine_name)
            
            if not engine:
                logger.warning(f"Failed to initialize {engine_name}, skipping")
                all_errors.append(f"Failed to initialize {engine_name}")
                continue
            
            try:
                # Get previews from this engine
                previews = engine._get_previews(query)
                
                # If search was successful, return results
                if previews and len(previews) > 0:
                    logger.info(f"Successfully got {len(previews)} preview results from {engine_name}")
                    # Store selected engine for later use
                    self._selected_engine = engine
                    self._selected_engine_name = engine_name
                    return previews
                
                logger.info(f"{engine_name} returned no previews")
                all_errors.append(f"{engine_name} returned no previews")
            
            except Exception as e:
                error_msg = f"Error getting previews from {engine_name}: {str(e)}"
                logger.error(error_msg)
                all_errors.append(error_msg)
        
        # If we reach here, all engines failed, use fallback
        logger.warning(f"All engines failed or returned no preview results: {', '.join(all_errors)}")
        logger.info("Using fallback Wikipedia engine for previews")
        self._selected_engine = self.fallback_engine
        self._selected_engine_name = "wikipedia"
        return self.fallback_engine._get_previews(query)
    
    def _get_full_content(self, relevant_items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        Get full content using the engine that provided the previews.
        
        Args:
            relevant_items: List of relevant preview dictionaries
            
        Returns:
            List of result dictionaries with full content
        """
        # Check if we should get full content
        if hasattr(config, 'SEARCH_SNIPPETS_ONLY') and config.SEARCH_SNIPPETS_ONLY:
            logger.info("Snippet-only mode, skipping full content retrieval")
            return relevant_items
            
        logger.info("Getting full content for relevant items")
        
        # Use the selected engine to get full content
        if hasattr(self, '_selected_engine'):
            try:
                logger.info(f"Using {self._selected_engine_name} to get full content")
                return self._selected_engine._get_full_content(relevant_items)
            except Exception as e:
                logger.error(f"Error getting full content from {self._selected_engine_name}: {str(e)}")
                # Fall back to returning relevant items without full content
                return relevant_items
        else:
            logger.warning("No engine was selected during preview phase, returning relevant items as-is")
            return relevant_items
    
    def _get_engine_instance(self, engine_name: str) -> Optional[BaseSearchEngine]:
        """Get or create an instance of the specified search engine"""
        # Return cached instance if available
        if engine_name in self.engine_cache:
            return self.engine_cache[engine_name]
        
        # Create a new instance
        engine = None
        try:
            # Only pass parameters that all engines accept
            common_params = {
                "llm": self.llm,
                "max_results": self.max_results
            }
            
            # Add max_filtered_results if specified
            if self.max_filtered_results is not None:
                common_params["max_filtered_results"] = self.max_filtered_results
                
            engine = create_search_engine(
                engine_name, 
                **common_params
            )
        except Exception as e:
            logger.error(f"Error creating engine instance for {engine_name}: {str(e)}")
            return None
        
        if engine:
            # Cache the instance
            self.engine_cache[engine_name] = engine
        
        return engine
    
    def invoke(self, query: str) -> List[Dict[str, Any]]:
        """Compatibility method for LangChain tools"""
        return self.run(query)