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

from langchain_core.language_models import BaseLLM

from ..search_engine_base import BaseSearchEngine
from ..rate_limiting import RateLimitError
from ...security import safe_post


class SerperSearchEngine(BaseSearchEngine):
    """Google search engine implementation using Serper API 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

    # Class constants
    BASE_URL = "https://google.serper.dev/search"
    DEFAULT_TIMEOUT = 30
    DEFAULT_REGION = "us"
    DEFAULT_LANGUAGE = "en"

    def __init__(
        self,
        max_results: int = 10,
        region: str = "us",
        time_period: Optional[str] = None,
        safe_search: bool = True,
        search_language: str = "en",
        api_key: Optional[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 Serper search engine.

        Args:
            max_results: Maximum number of search results (default 10)
            region: Country code for localized results (e.g., 'us', 'gb', 'fr')
            time_period: Time filter for results ('day', 'week', 'month', 'year', or None for all time)
            safe_search: Whether to enable safe search
            search_language: Language code for results (e.g., 'en', 'es', 'fr')
            api_key: Serper API key (can also be set in settings)
            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,
        )
        self.region = region
        self.time_period = time_period
        self.safe_search = safe_search
        self.search_language = search_language

        # Get API key - check params, settings, or env vars
        serper_api_key = self._resolve_api_key(
            api_key,
            "search.engine.web.serper.api_key",
            engine_name="Serper",
            settings_snapshot=settings_snapshot,
        )

        self.api_key = serper_api_key
        self.base_url = self.BASE_URL
        # Note: self.engine_type is automatically set by parent BaseSearchEngine class

        # Initialize per-query attributes (reset in _get_previews per search)
        self._knowledge_graph = None

        # If full content is requested, initialize FullSearchResults
        self._init_full_search(
            web_search=None,  # We'll handle the search ourselves
            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 Serper API.

        Args:
            query: The search query

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

        # Reset per-query attributes to prevent leakage between searches
        self._knowledge_graph = None

        try:
            # Build request payload
            payload = {
                "q": query,
                # Google's "num" tops out at 100 results per request; cap so a
                # large user-supplied max_results can't request an unbounded page.
                "num": min(self.max_results, 100),
                "gl": self.region,
                "hl": self.search_language,
            }

            # Add optional parameters
            if self.time_period:
                # Map time periods to Serper's format
                time_mapping = {
                    "day": "d",
                    "week": "w",
                    "month": "m",
                    "year": "y",
                }
                if self.time_period in time_mapping:
                    payload["tbs"] = f"qdr:{time_mapping[self.time_period]}"

            # Apply rate limiting before request
            self._last_wait_time = self.rate_tracker.apply_rate_limit(
                self.engine_type
            )

            # Make API request
            headers = {
                "X-API-KEY": self.api_key,
                "Content-Type": "application/json",
            }

            response = safe_post(
                self.base_url,
                headers=headers,
                json=payload,
                timeout=self.DEFAULT_TIMEOUT,
            )

            # Check for rate limits
            self._raise_if_rate_limit(response.status_code)

            response.raise_for_status()

            data = response.json()

            # Extract organic results
            organic_results = data.get("organic", [])

            # Format results as previews
            previews = []
            for idx, result in enumerate(organic_results):
                # Extract display link
                link = result.get("link", "")
                display_link = self._extract_display_link(link)

                preview = {
                    "id": idx,
                    "title": result.get("title", ""),
                    "link": link,
                    "snippet": result.get("snippet", ""),
                    "displayed_link": display_link,
                    "position": result.get("position", idx + 1),
                }

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

                # Only include optional fields if present to avoid None values
                # This keeps the preview dict cleaner and saves memory
                if "sitelinks" in result:
                    preview["sitelinks"] = result["sitelinks"]

                if "date" in result:
                    preview["date"] = result["date"]

                if "attributes" in result:
                    preview["attributes"] = result["attributes"]

                previews.append(preview)

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

            # Also store knowledge graph if available
            if "knowledgeGraph" in data:
                self._knowledge_graph = data["knowledgeGraph"]
                logger.info(
                    f"Found knowledge graph for query: {data['knowledgeGraph'].get('title', 'Unknown')}"
                )

            return previews

        except RateLimitError:
            raise  # Re-raise rate limit errors
        except requests.exceptions.RequestException as e:
            safe_msg = self._scrub_error(e)
            logger.warning(f"Error getting Serper API results: {safe_msg}")
            self._raise_if_rate_limit(e)
            return []
        except Exception as e:
            safe_msg = self._scrub_error(e)
            logger.warning(
                f"Unexpected error getting Serper API results: {safe_msg}"
            )
            return []

    def _get_full_content(
        self, relevant_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Get full content for the relevant search results.
        Extends base implementation to include knowledge graph data.

        Args:
            relevant_items: List of relevant preview dictionaries

        Returns:
            List of result dictionaries with full content if requested
        """
        results = super()._get_full_content(relevant_items)

        # Include knowledge graph if available
        if results and hasattr(self, "_knowledge_graph"):
            results[0]["knowledge_graph"] = self._knowledge_graph

        return results

    def _temp_attributes(self):
        """Return list of temporary attribute names to clean up after run()."""
        return super()._temp_attributes() + [
            "_knowledge_graph",
        ]
