from loguru import logger
from typing import Any, Dict, List, Optional

from langchain_community.utilities import SerpAPIWrapper
from langchain_core.language_models import BaseLLM

from ..rate_limiting import RateLimitError
from ..search_engine_base import BaseSearchEngine


class SerpAPISearchEngine(BaseSearchEngine):
    """Google search engine implementation using SerpAPI with two-phase approach"""

    # Mark as public search engine
    is_public = True
    # Mark as generic search engine (general web search via Google)
    is_generic = True

    def __init__(
        self,
        max_results: int = 10,
        region: str = "us",
        time_period: str = "y",
        safe_search: bool = True,
        search_language: str = "English",
        api_key: Optional[str] = None,
        language_code_mapping: Optional[Dict[str, str]] = None,
        llm: Optional[BaseLLM] = None,
        include_full_content: bool = False,
        max_filtered_results: Optional[int] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """
        Initialize the SerpAPI search engine.

        Args:
            max_results: Maximum number of search results
            region: Region code for search results
            time_period: Time period for search results
            safe_search: Whether to enable safe search
            search_language: Language for search results
            api_key: SerpAPI API key (can also be set via LDR_SEARCH_ENGINE_WEB_SERPAPI_API_KEY env var or in UI settings)
            language_code_mapping: Mapping from language names to codes
            llm: Language model for relevance filtering
            include_full_content: Whether to include full webpage content in results
            max_filtered_results: Maximum number of results to keep after filtering
            settings_snapshot: Settings snapshot for thread context
            **kwargs: Additional parameters (ignored but accepted for compatibility)
        """
        # Initialize the BaseSearchEngine with LLM, max_filtered_results, and max_results
        super().__init__(
            llm=llm,
            max_filtered_results=max_filtered_results,
            max_results=max_results,
            include_full_content=include_full_content,
            settings_snapshot=settings_snapshot,
        )

        # Set up language code mapping
        if language_code_mapping is None:
            from ...utilities.search_utilities import LANGUAGE_CODE_MAP

            language_code_mapping = LANGUAGE_CODE_MAP

        # Get API key - check params, settings, or env vars
        serpapi_api_key = self._resolve_api_key(
            api_key,
            "search.engine.web.serpapi.api_key",
            engine_name="SerpAPI",
            settings_snapshot=settings_snapshot,
        )
        # Store for error-message redaction (BaseSearchEngine._scrub_error
        # reads self.api_key via _secret_attrs). SerpAPIWrapper sends the key
        # as a URL param the regex pass already catches, but storing it gives
        # the literal-redaction pass a belt-and-suspenders for any other shape.
        self.api_key = serpapi_api_key

        # Get language code
        language_code = language_code_mapping.get(search_language.lower(), "en")

        # Initialize SerpAPI wrapper
        self.engine = SerpAPIWrapper(
            serpapi_api_key=serpapi_api_key,
            params={
                "engine": "google",
                "hl": language_code,
                "gl": region,
                "safe": "active" if safe_search else "off",
                "tbs": f"qdr:{time_period}",
                # Google's "num" tops out at 100 results per request; cap so a
                # large user-supplied max_results can't request an unbounded page.
                # Use the base-class-normalized self.max_results (positive int)
                # rather than the raw arg, which could be None/str.
                "num": min(self.max_results, 100),
            },
        )

        # If full content is requested, initialize FullSearchResults
        self._init_full_search(
            web_search=self.engine,
            language=search_language,
            max_results=max_results,
            region=region,
            time_period=time_period,
            safe_search="Moderate" if safe_search else "Off",
        )

    def _get_previews(self, query: str) -> List[Dict[str, Any]]:
        """
        Get preview information from SerpAPI.

        Args:
            query: The search query

        Returns:
            List of preview dictionaries
        """
        logger.info("Getting search results from SerpAPI")

        try:
            # Get search results from SerpAPI
            organic_results = self.engine.results(query).get(
                "organic_results", []
            )

            # Format results as previews
            previews: list[dict[str, Any]] = []
            for result in organic_results:
                preview = {
                    "id": result.get(
                        "position", len(previews)
                    ),  # Use position as ID
                    "title": result.get("title", ""),
                    "link": result.get("link", ""),
                    "snippet": result.get("snippet", ""),
                    "displayed_link": result.get("displayed_link", ""),
                    "position": result.get("position"),
                }

                # Store full SerpAPI result for later
                preview["_full_result"] = result

                previews.append(preview)

            # Store the previews for potential full content retrieval
            self._search_results = previews

            return previews

        except RateLimitError:
            raise
        except Exception as e:
            safe_msg = self._scrub_error(e)
            logger.warning(f"Error getting SerpAPI results: {safe_msg}")
            self._raise_if_rate_limit(e)
            return []
