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

from ...utilities.db_utils import get_db_setting
from ...web.services.socket_service import emit_socket_event
from ..search_engine_base import BaseSearchEngine
from ..search_engine_factory import create_search_engine
from ..search_engines_config import search_config
from .search_engine_wikipedia import WikipediaSearchEngine

# 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,
        engine_selection_callback=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, max_filtered_results, and max_results
        super().__init__(
            llm=llm, max_filtered_results=max_filtered_results, 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=self.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', based on user settings"""
        # Filter out 'meta' and 'auto' and check API key availability
        available = []

        for name, config_ in search_config().items():
            if name in ["meta", "auto"]:
                continue

            # Determine if this is a local engine (starts with "local.")
            is_local_engine = name.startswith("local.")

            # Determine the appropriate setting path based on engine type
            if is_local_engine:
                # Format: search.engine.local.{engine_name}.use_in_auto_search
                local_name = name.replace("local.", "")
                auto_search_setting = (
                    f"search.engine.local.{local_name}.use_in_auto_search"
                )
            else:
                # Format: search.engine.web.{engine_name}.use_in_auto_search
                auto_search_setting = f"search.engine.web.{name}.use_in_auto_search"

            # Get setting from database, default to False if not found
            use_in_auto_search = get_db_setting(auto_search_setting, False)

            # Skip engines that aren't enabled for auto search
            if not use_in_auto_search:
                logger.info(
                    f"Skipping {name} engine because it's not enabled for auto search"
                )
                continue

            # Skip engines that require API keys if we don't want to use them
            if config_.get("requires_api_key", False) and not self.use_api_key_services:
                continue

            # Skip engines that require API keys if the key is not available
            if config_.get("requires_api_key", False):
                api_key = config_.get("api_key")
                if not api_key:
                    continue

            available.append(name)

        # If no engines are available, raise an error instead of falling back silently
        if not available:
            error_msg = "No search engines enabled for auto search. Please enable at least one engine in settings."
            logger.error(error_msg)
            raise RuntimeError(error_msg)

        return available

    def analyze_query(self, query: str) -> List[str]:
        """
        Analyze the query to determine the best search engines to use.

        Args:
            query: The search query

        Returns:
            List of search engine names sorted by suitability
        """
        try:
            # Check if the LLM is available to help select engines
            if not self.llm:
                logger.warning(
                    "No LLM available for query analysis, using default engines"
                )
                # Return engines sorted by reliability
                return sorted(
                    self.available_engines,
                    key=lambda x: search_config().get(x, {}).get("reliability", 0),
                    reverse=True,
                )

            # Create a prompt that outlines the available search engines and their strengths
            engines_info = []
            for engine_name in self.available_engines:
                try:
                    if engine_name in search_config():
                        strengths = search_config()[engine_name].get(
                            "strengths", "General search"
                        )
                        weaknesses = search_config()[engine_name].get(
                            "weaknesses", "None specified"
                        )
                        description = search_config()[engine_name].get(
                            "description", engine_name
                        )
                        engines_info.append(
                            f"- {engine_name}: {description}\n  Strengths: {strengths}\n  Weaknesses: {weaknesses}"
                        )
                except KeyError as e:
                    logger.error(f"Missing key for engine {engine_name}: {str(e)}")

            # Only proceed if we have engines available to choose from
            if not engines_info:
                logger.warning(
                    "No engine information available for prompt, using reliability-based sorting instead"
                )
                return sorted(
                    self.available_engines,
                    key=lambda x: search_config().get(x, {}).get("reliability", 0),
                    reverse=True,
                )

            prompt = f"""You are a search query analyst. Consider this search query:

QUERY: {query}

I have these search engines available:
{chr(10).join(engines_info)}

Determine which search engines would be most appropriate for answering this query.
First analyze the nature of the query (factual, scientific, code-related, etc.)
Then select the 1-3 most appropriate search engines for this type of query.

Output ONLY a comma-separated list of the search engine names in order of most appropriate to least appropriate.
Example output: wikipedia,arxiv,github"""

            # Get analysis from LLM
            response = self.llm.invoke(prompt)

            # Handle different response formats
            if hasattr(response, "content"):
                content = response.content.strip()
            else:
                content = str(response).strip()

            # Extract engine names
            valid_engines = []
            for engine_name in content.split(","):
                cleaned_name = engine_name.strip().lower()
                if cleaned_name in self.available_engines:
                    valid_engines.append(cleaned_name)

            # 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_config().get(x, {}).get("reliability", 0),
                    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_config().get(x, {}).get("reliability", 0),
                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: 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"ENGINE_SELECTED: {engine_name}")
                    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

                    # Emit a socket event to inform about the selected engine
                    try:
                        emit_socket_event(
                            "search_engine_selected",
                            {"engine": engine_name, "result_count": len(previews)},
                        )
                    except Exception as socket_error:
                        logger.error(
                            f"Socket emit error (non-critical): {str(socket_error)}"
                        )

                    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 get_db_setting("search.snippets_only", True):
            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)
