from loguru import logger
from typing import Any, Dict, List, Optional
import requests
from urllib.parse import urlparse

from langchain_core.language_models import BaseLLM

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


class ScaleSerpSearchEngine(BaseSearchEngine):
    """Google search engine implementation using ScaleSerp API with caching support"""

    # 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,
        location: str = "United States",
        language: str = "en",
        device: str = "desktop",
        safe_search: bool = True,
        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,
        enable_cache: bool = True,
        **kwargs,
    ):
        """
        Initialize the ScaleSerp search engine.

        Args:
            max_results: Maximum number of search results (default 10, max 100)
            location: Location for localized results (e.g., 'United States', 'London,England,United Kingdom')
            language: Language code for results (e.g., 'en', 'es', 'fr')
            device: Device type for search ('desktop' or 'mobile')
            safe_search: Whether to enable safe search
            api_key: ScaleSerp 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
            enable_cache: Whether to use ScaleSerp's 1-hour caching (saves costs for repeated searches)
            **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,
        )
        self.include_full_content = include_full_content
        self.location = location
        self.language = language
        self.device = device
        self.safe_search = safe_search
        self.enable_cache = enable_cache  # ScaleSerp's unique caching feature

        # Get API key - check params, env vars, or database
        from ...config.search_config import get_setting_from_snapshot

        scaleserp_api_key = api_key
        if not scaleserp_api_key:
            scaleserp_api_key = get_setting_from_snapshot(
                "search.engine.web.scaleserp.api_key",
                settings_snapshot=settings_snapshot,
            )

        if not scaleserp_api_key:
            raise ValueError(
                "ScaleSerp API key not found. Please provide api_key parameter or set it in the UI settings. "
                "Get your API key at https://scaleserp.com"
            )

        self.api_key = scaleserp_api_key
        self.base_url = "https://api.scaleserp.com/search"

        # If full content is requested, initialize FullSearchResults
        if include_full_content:
            # Import FullSearchResults only if needed
            try:
                from .full_search import FullSearchResults

                self.full_search = FullSearchResults(
                    llm=llm,
                    web_search=None,  # We'll handle the search ourselves
                    language=language,
                    max_results=max_results,
                    region=location,
                    time=None,
                    safesearch="Moderate" if safe_search else "Off",
                )
            except ImportError:
                logger.warning(
                    "Warning: FullSearchResults not available. Full content retrieval disabled."
                )
                self.include_full_content = False

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

        Args:
            query: The search query

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

        try:
            # Build request parameters
            params = {
                "api_key": self.api_key,
                "q": query,
                "num": min(self.max_results, 100),  # ScaleSerp max is 100
                "location": self.location,
                "hl": self.language,
                "device": self.device,
            }

            # Add safe search if enabled
            if self.safe_search:
                params["safe"] = "on"

            # ScaleSerp automatically caches identical queries for 1 hour
            # Cached results are served instantly and don't consume API credits
            if self.enable_cache:
                params["output"] = (
                    "json"  # Ensure JSON output for cache detection
                )
                logger.debug(
                    "ScaleSerp caching enabled - identical searches within 1 hour are free"
                )

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

            # Make API request
            response = safe_get(self.base_url, params=params, timeout=30)

            # Check for rate limits
            if response.status_code == 429:
                raise RateLimitError(
                    f"ScaleSerp rate limit hit: {response.status_code} - {response.text}"
                )

            response.raise_for_status()

            data = response.json()

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

            # Format results as previews
            previews = []

            # Check if results were served from cache for monitoring
            from_cache = data.get("request_info", {}).get("cached", False)

            for idx, result in enumerate(organic_results):
                # Extract display link safely using urlparse
                link = result.get("link", "")
                display_link = ""
                if link:
                    try:
                        parsed_url = urlparse(link)
                        display_link = (
                            parsed_url.netloc or parsed_url.path or ""
                        )
                    except Exception:
                        # Fallback to truncated URL if parsing fails
                        display_link = link[:50]

                preview = {
                    "id": idx,
                    "title": result.get("title", ""),
                    "link": link,
                    "snippet": result.get("snippet", ""),
                    "displayed_link": display_link,
                    "position": result.get("position", idx + 1),
                    "from_cache": from_cache,  # Add cache status for monitoring
                }

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

                # Include rich snippets if available
                if "rich_snippet" in result:
                    preview["rich_snippet"] = result["rich_snippet"]

                # Include date if available
                if "date" in result:
                    preview["date"] = result["date"]

                # Include sitelinks if available
                if "sitelinks" in result:
                    preview["sitelinks"] = result["sitelinks"]

                previews.append(preview)

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

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

            # Store related searches
            if "related_searches" in data:
                self._related_searches = data["related_searches"]

            # Store related questions (People Also Ask)
            if "related_questions" in data:
                self._related_questions = data["related_questions"]

            # Log if result was served from cache
            if from_cache:
                logger.debug(
                    "Result served from ScaleSerp cache - no API credit used!"
                )

            return previews

        except RateLimitError:
            raise  # Re-raise rate limit errors
        except requests.exceptions.RequestException as e:
            error_msg = str(e)
            logger.exception(
                "Error getting ScaleSerp API results. Check API docs: https://docs.scaleserp.com"
            )

            # Check for rate limit patterns in error message
            if any(
                pattern in error_msg.lower()
                for pattern in [
                    "429",
                    "rate limit",
                    "quota",
                    "too many requests",
                ]
            ):
                raise RateLimitError(f"ScaleSerp rate limit hit: {error_msg}")

            return []
        except Exception:
            logger.exception("Unexpected error getting ScaleSerp API results")
            return []

    def _get_full_content(
        self, relevant_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Get full content for the relevant search results.
        If include_full_content is True and FullSearchResults is available,
        retrieves full webpage content for the results.

        Args:
            relevant_items: List of relevant preview dictionaries

        Returns:
            List of result dictionaries with full content if requested
        """
        # Check if we should get full content
        from ...config import search_config

        if (
            hasattr(search_config, "SEARCH_SNIPPETS_ONLY")
            and search_config.SEARCH_SNIPPETS_ONLY
        ):
            logger.info("Snippet-only mode, skipping full content retrieval")

            # Return the relevant items with their full ScaleSerp information
            results = []
            for item in relevant_items:
                # Use the full result if available, otherwise use the preview
                if "_full_result" in item:
                    result = item["_full_result"].copy()
                else:
                    result = item.copy()

                # Clean up temporary fields
                if "_full_result" in result:
                    del result["_full_result"]

                results.append(result)

            # Include knowledge graph and other metadata if this is the first call
            if results and hasattr(self, "_knowledge_graph"):
                results[0]["knowledge_graph"] = self._knowledge_graph

            return results

        # If full content retrieval is enabled
        if self.include_full_content and hasattr(self, "full_search"):
            logger.info("Retrieving full webpage content")

            try:
                # Use FullSearchResults to get full content
                results_with_content = self.full_search._get_full_content(
                    relevant_items
                )

                return results_with_content

            except Exception as e:
                logger.info(f"Error retrieving full content: {e}")
                # Fall back to returning the items without full content

        # Return items with their full ScaleSerp information
        results = []
        for item in relevant_items:
            # Use the full result if available, otherwise use the preview
            if "_full_result" in item:
                result = item["_full_result"].copy()
            else:
                result = item.copy()

            # Clean up temporary fields
            if "_full_result" in result:
                del result["_full_result"]

            results.append(result)

        # Include knowledge graph and other metadata if this is the first call
        if results and hasattr(self, "_knowledge_graph"):
            results[0]["knowledge_graph"] = self._knowledge_graph

        return results

    def run(
        self, query: str, research_context: Dict[str, Any] | None = None
    ) -> List[Dict[str, Any]]:
        """
        Execute a search using ScaleSerp API with the two-phase approach.

        Args:
            query: The search query
            research_context: Context from previous research to use.

        Returns:
            List of search results
        """
        logger.info("---Execute a search using ScaleSerp API (Google)---")

        # Use the implementation from the parent class which handles all phases
        results = super().run(query, research_context=research_context)

        # Clean up
        if hasattr(self, "_search_results"):
            del self._search_results
        if hasattr(self, "_knowledge_graph"):
            del self._knowledge_graph
        if hasattr(self, "_related_searches"):
            del self._related_searches
        if hasattr(self, "_related_questions"):
            del self._related_questions

        return results
