import importlib
import inspect
import logging
import os
from typing import Any, Dict, Optional

from ..utilities.db_utils import get_db_setting
from .search_engine_base import BaseSearchEngine
from .search_engines_config import default_search_engine, search_config

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


def create_search_engine(
    engine_name: str, llm=None, **kwargs
) -> Optional[BaseSearchEngine]:
    """
    Create a search engine instance based on the engine name.

    Args:
        engine_name: Name of the search engine to create
        llm: Language model instance (required for some engines like meta)
        **kwargs: Additional parameters to override defaults

    Returns:
        Initialized search engine instance or None if creation failed
    """
    # If engine name not found, use default
    if engine_name not in search_config():
        logger.warning(
            f"Search engine '{engine_name}' not found, using default: "
            f"{default_search_engine()}"
        )
        engine_name = default_search_engine()

    # Get engine configuration
    engine_config = search_config()[engine_name]

    # Set default max_results from config if not provided in kwargs
    if "max_results" not in kwargs:
        max_results = get_db_setting("search.max_results", 10)
        if max_results is None:
            max_results = 20
        kwargs["max_results"] = max_results

    # Check for API key requirements
    if engine_config.get("requires_api_key", False):
        api_key = os.getenv(f"LDR_{engine_name.upper()}_API_KEY")
        if not api_key:
            api_key = engine_config.get("api_key")

        if not api_key:
            logger.info(f"Required API key for {engine_name} not found in settings.")
            return None

    # Check for LLM requirements
    if engine_config.get("requires_llm", False) and not llm:
        logger.info(
            f"Engine {engine_name} requires an LLM instance but none was provided"
        )
        return None

    try:
        # Load the engine class
        module_path = engine_config["module_path"]
        class_name = engine_config["class_name"]

        package = None
        if module_path.startswith("."):
            # This is a relative import. Assume it's relative to
            # `web_search_engines`.
            package = "local_deep_research.web_search_engines"
        module = importlib.import_module(module_path, package=package)
        engine_class = getattr(module, class_name)

        # Get the engine class's __init__ parameters to filter out unsupported ones
        engine_init_signature = inspect.signature(engine_class.__init__)
        engine_init_params = list(engine_init_signature.parameters.keys())

        # Combine default parameters with provided ones
        all_params = {**engine_config.get("default_params", {}), **kwargs}

        # Filter out parameters that aren't accepted by the engine class
        # Note: 'self' is always the first parameter of instance methods, so we skip it
        filtered_params = {
            k: v for k, v in all_params.items() if k in engine_init_params[1:]
        }

        # Add LLM if required
        if engine_config.get("requires_llm", False):
            filtered_params["llm"] = llm

        # Add API key if required and not already in filtered_params
        if (
            engine_config.get("requires_api_key", False)
            and "api_key" not in filtered_params
        ):
            api_key_env = engine_config.get("api_key_env")
            if api_key_env:
                api_key = os.getenv(api_key_env)
                if api_key:
                    filtered_params["api_key"] = api_key

        logger.info(
            f"Creating {engine_name} with filtered parameters: {filtered_params.keys()}"
        )

        # Create the engine instance with filtered parameters
        engine = engine_class(**filtered_params)

        # Check if we need to wrap with full search capabilities
        if kwargs.get("use_full_search", False) and engine_config.get(
            "supports_full_search", False
        ):
            return _create_full_search_wrapper(engine_name, engine, llm, kwargs)

        return engine

    except Exception as e:
        logger.info(f"Failed to create search engine '{engine_name}': {str(e)}")
        return None


def _create_full_search_wrapper(
    engine_name: str, base_engine: BaseSearchEngine, llm, params: Dict[str, Any]
) -> Optional[BaseSearchEngine]:
    """Create a full search wrapper for the base engine if supported"""
    try:
        engine_config = search_config()[engine_name]

        # Get full search class details
        module_path = engine_config.get("full_search_module")
        class_name = engine_config.get("full_search_class")

        if not module_path or not class_name:
            logger.warning(f"Full search configuration missing for {engine_name}")
            return base_engine

        # Import the full search class
        module = importlib.import_module(module_path)
        full_search_class = getattr(module, class_name)

        # Get the wrapper's __init__ parameters to filter out unsupported ones
        wrapper_init_signature = inspect.signature(full_search_class.__init__)
        wrapper_init_params = list(wrapper_init_signature.parameters.keys())[
            1:
        ]  # Skip 'self'

        # Extract relevant parameters for the full search wrapper
        wrapper_params = {k: v for k, v in params.items() if k in wrapper_init_params}

        # Special case for SerpAPI which needs the API key directly
        if engine_name == "serpapi" and "serpapi_api_key" in wrapper_init_params:
            serpapi_api_key = os.getenv("SERP_API_KEY")
            if serpapi_api_key:
                wrapper_params["serpapi_api_key"] = serpapi_api_key

            # Map some parameter names to what the wrapper expects
            if (
                "language" in params
                and "search_language" not in params
                and "language" in wrapper_init_params
            ):
                wrapper_params["language"] = params["language"]

            if (
                "safesearch" not in wrapper_params
                and "safe_search" in params
                and "safesearch" in wrapper_init_params
            ):
                wrapper_params["safesearch"] = (
                    "active" if params["safe_search"] else "off"
                )

        # Special case for Brave which needs the API key directly
        if engine_name == "brave" and "api_key" in wrapper_init_params:
            brave_api_key = os.getenv("BRAVE_API_KEY")
            if brave_api_key:
                wrapper_params["api_key"] = brave_api_key

            # Map some parameter names to what the wrapper expects
            if (
                "language" in params
                and "search_language" not in params
                and "language" in wrapper_init_params
            ):
                wrapper_params["language"] = params["language"]

            if (
                "safesearch" not in wrapper_params
                and "safe_search" in params
                and "safesearch" in wrapper_init_params
            ):
                wrapper_params["safesearch"] = (
                    "moderate" if params["safe_search"] else "off"
                )

        # Always include llm if it's a parameter
        if "llm" in wrapper_init_params:
            wrapper_params["llm"] = llm

        # If the wrapper needs the base engine and has a parameter for it
        if "web_search" in wrapper_init_params:
            wrapper_params["web_search"] = base_engine

        logger.debug(
            f"Creating full search wrapper for {engine_name} with filtered parameters: {wrapper_params.keys()}"
        )

        # Create the full search wrapper with filtered parameters
        full_search = full_search_class(**wrapper_params)

        return full_search

    except Exception as e:
        logger.error(
            f"Failed to create full search wrapper for {engine_name}: {str(e)}"
        )
        return base_engine


def get_search(
    search_tool: str,
    llm_instance,
    max_results: int = 10,
    region: str = "us",
    time_period: str = "y",
    safe_search: bool = True,
    search_snippets_only: bool = False,
    search_language: str = "English",
    max_filtered_results: Optional[int] = None,
):
    """
    Get search tool instance based on the provided parameters.

    Args:
        search_tool: Name of the search engine to use
        llm_instance: Language model instance
        max_results: Maximum number of search results
        region: Search region/locale
        time_period: Time period for search results
        safe_search: Whether to enable safe search
        search_snippets_only: Whether to return just snippets (vs. full content)
        search_language: Language for search results
        max_filtered_results: Maximum number of results to keep after filtering

    Returns:
        Initialized search engine instance
    """
    # Common parameters
    params = {
        "max_results": max_results,
        "llm": llm_instance,  # Only used by engines that need it
    }

    # Add max_filtered_results if provided
    if max_filtered_results is not None:
        params["max_filtered_results"] = max_filtered_results

    # Add engine-specific parameters
    if search_tool in ["duckduckgo", "serpapi", "google_pse", "brave"]:
        params.update(
            {
                "region": region,
                "safe_search": safe_search,
                "use_full_search": not search_snippets_only,
            }
        )

    if search_tool in ["serpapi", "brave", "google_pse"]:
        params["search_language"] = search_language

    if search_tool == "serpapi":
        params["time_period"] = time_period

    # Create and return the search engine
    logger.info(
        f"Creating search engine for tool: {search_tool} with params: {params.keys()}"
    )
    engine = create_search_engine(search_tool, **params)

    # Add debugging to check if engine is None
    if engine is None:
        logger.error(
            f"Failed to create search engine for {search_tool} - returned None"
        )
    else:
        engine_type = type(engine).__name__
        logger.info(f"Successfully created search engine of type: {engine_type}")
        # Check if the engine has run method
        if hasattr(engine, "run"):
            logger.info(f"Engine has 'run' method: {getattr(engine, 'run')}")
        else:
            logger.error("Engine does NOT have 'run' method!")

        # For SearxNG, check availability flag
        if hasattr(engine, "is_available"):
            logger.info(f"Engine availability flag: {engine.is_available}")

    return engine
