"""
Calls SearchAPI.io's Google Search API endpoint.

SearchAPI.io API Reference: https://www.searchapi.io/docs/google
"""

from typing import Dict, List, Literal, Optional, TypedDict, Union, cast
from urllib.parse import urlencode

import httpx

from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.llms.base_llm.search.transformation import (
    BaseSearchConfig,
    SearchResponse,
    SearchResult,
)
from litellm.secret_managers.main import get_secret_str


class _SearchAPIRequestRequired(TypedDict):
    """Required fields for SearchAPI.io request."""

    engine: str  # Required - search engine (e.g., 'google')
    q: str  # Required - search query


class SearchAPIRequest(_SearchAPIRequestRequired, total=False):
    """
    SearchAPI.io request format for Google Search.
    Based on: https://www.searchapi.io/docs/google
    """

    kgmid: str  # Optional - Knowledge Graph identifier
    device: str  # Optional - device type ('desktop', 'mobile', 'tablet')
    location: str  # Optional - geographic location
    uule: str  # Optional - Google-encoded location
    google_domain: str  # Optional - Google domain (deprecated)
    gl: str  # Optional - country code (e.g., 'us', 'uk')
    hl: str  # Optional - interface language (e.g., 'en', 'es')
    lr: str  # Optional - language restriction (e.g., 'lang_en')
    cr: str  # Optional - country restriction
    nfpr: int  # Optional - exclude auto-corrected results (0 or 1)
    filter: int  # Optional - duplicate/host crowding filter (0 or 1)
    safe: str  # Optional - SafeSearch ('active', 'off')
    time_period: str  # Optional - time period ('last_hour', 'last_day', 'last_week', 'last_month', 'last_year')
    time_period_min: str  # Optional - start date (MM/DD/YYYY)
    time_period_max: str  # Optional - end date (MM/DD/YYYY)
    num: int  # Optional - number of results (phased out by Google, constant 10)
    page: int  # Optional - page number for pagination
    optimization_strategy: str  # Optional - 'performance' or 'ads'


class SearchAPIConfig(BaseSearchConfig):
    SEARCHAPI_API_BASE = "https://www.searchapi.io/api/v1/search"

    @staticmethod
    def ui_friendly_name() -> str:
        return "SearchAPI.io (Google Search)"

    def get_http_method(self) -> Literal["GET", "POST"]:
        """
        SearchAPI.io uses GET requests for search.
        """
        return "GET"

    def validate_environment(
        self,
        headers: Dict,
        api_key: Optional[str] = None,
        api_base: Optional[str] = None,
        **kwargs,
    ) -> Dict:
        """
        Validate environment and return headers.
        """
        api_key = api_key or get_secret_str("SEARCHAPI_API_KEY")

        if not api_key:
            raise ValueError(
                "SEARCHAPI_API_KEY is not set. Set `SEARCHAPI_API_KEY` environment variable."
            )

        headers["Content-Type"] = "application/json"

        return headers

    def get_complete_url(
        self,
        api_base: Optional[str],
        optional_params: dict,
        data: Optional[Union[Dict, List[Dict]]] = None,
        **kwargs,
    ) -> str:
        """
        Get complete URL for Search endpoint with query parameters.

        SearchAPI.io uses GET requests and includes api_key in query params.
        """
        api_base = (
            api_base or get_secret_str("SEARCHAPI_API_BASE") or self.SEARCHAPI_API_BASE
        )

        # Build query parameters from the transformed request body
        if data and isinstance(data, dict) and "_searchapi_params" in data:
            params = data["_searchapi_params"]
            query_string = urlencode(params, doseq=True)
            return f"{api_base}?{query_string}"

        return api_base

    def transform_search_request(
        self,
        query: Union[str, List[str]],
        optional_params: dict,
        api_key: Optional[str] = None,
        search_engine_id: Optional[str] = None,
        **kwargs,
    ) -> Dict:
        """
        Transform Search request to SearchAPI.io format.

        Transforms unified spec parameters:
        - query → q
        - max_results → num (limited to 10 by Google)
        - search_domain_filter → q (append site: filters)
        - country → gl

        Args:
            query: Search query (string or list of strings)
            optional_params: Optional parameters for the request
            api_key: API key for authentication

        Returns:
            Dict with typed request data following SearchAPI.io spec
        """
        if isinstance(query, list):
            query = " ".join(query)

        # Get API key from parameter or environment
        api_key = api_key or get_secret_str("SEARCHAPI_API_KEY")
        if not api_key:
            raise ValueError(
                "SEARCHAPI_API_KEY is not set. Set `SEARCHAPI_API_KEY` environment variable."
            )

        request_data: SearchAPIRequest = {
            "engine": "google",
            "q": query,
        }

        # Add API key to request
        result_data = dict(request_data)
        result_data["api_key"] = api_key

        # Transform unified spec parameters to SearchAPI.io format
        if "max_results" in optional_params:
            # Google now returns constant 10 results, but we can still set num
            num_results = min(optional_params["max_results"], 10)
            result_data["num"] = num_results

        if "search_domain_filter" in optional_params:
            # Convert to multiple "site:domain" clauses
            domains = optional_params["search_domain_filter"]
            if isinstance(domains, list) and len(domains) > 0:
                result_data["q"] = self._append_domain_filters(
                    str(result_data["q"]), domains
                )

        if "country" in optional_params:
            # Map to gl parameter
            result_data["gl"] = cast(str, optional_params["country"]).lower()

        # Pass through all other SearchAPI.io-specific parameters
        for param, value in optional_params.items():
            if (
                param not in self.get_supported_perplexity_optional_params()
                and param not in result_data
            ):
                result_data[param] = value

        # Store params in special key for URL building (GET request)
        return {
            "_searchapi_params": result_data,
        }

    @staticmethod
    def _append_domain_filters(query: str, domains: List[str]) -> str:
        """
        Add site: filters to restrict search to specific domains.
        """
        domain_clauses = [f"site:{domain}" for domain in domains]
        domain_query = " OR ".join(domain_clauses)

        return f"({query}) AND ({domain_query})"

    def transform_search_response(
        self,
        raw_response: httpx.Response,
        logging_obj: Optional[LiteLLMLoggingObj],
        **kwargs,
    ) -> SearchResponse:
        """
        Transform SearchAPI.io response to LiteLLM unified SearchResponse format.

        SearchAPI.io → LiteLLM mappings:
        - organic_results[].title → SearchResult.title
        - organic_results[].link → SearchResult.url
        - organic_results[].snippet → SearchResult.snippet
        - organic_results[].date → SearchResult.date
        """
        response_json = raw_response.json()

        # Transform results to SearchResult objects
        results: List[SearchResult] = []

        # Process organic results
        for result in response_json.get("organic_results", []):
            title = result.get("title", "")
            url = result.get("link", "")
            snippet = result.get("snippet", "")
            date = result.get("date")  # SearchAPI.io provides date in some results

            search_result = SearchResult(
                title=title,
                url=url,
                snippet=snippet,
                date=date,
                last_updated=None,  # SearchAPI.io doesn't provide last_updated
            )

            results.append(search_result)

        return SearchResponse(
            results=results,
            object="search",
        )
