"""
Calls Tavily's /search endpoint to search the web.

Tavily API Reference: https://docs.tavily.com/documentation/api-reference/endpoint/search
"""
from typing import Dict, List, Optional, TypedDict, Union

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 _TavilySearchRequestRequired(TypedDict):
    """Required fields for Tavily Search API request."""
    query: str  # Required - search query


class TavilySearchRequest(_TavilySearchRequestRequired, total=False):
    """
    Tavily Search API request format.
    Based on: https://docs.tavily.com/documentation/api-reference/endpoint/search
    """
    max_results: int  # Optional - maximum number of results (0-20), default 5
    include_domains: List[str]  # Optional - list of domains to include (max 300)
    exclude_domains: List[str]  # Optional - list of domains to exclude (max 150)
    topic: str  # Optional - category of search ('general', 'news', 'finance'), default 'general'
    search_depth: str  # Optional - depth of search ('basic', 'advanced'), default 'basic'
    include_answer: Union[bool, str]  # Optional - include LLM-generated answer
    include_raw_content: Union[bool, str]  # Optional - include raw HTML content
    include_images: bool  # Optional - perform image search
    include_image_descriptions: bool  # Optional - add descriptions for images
    include_favicon: bool  # Optional - include favicon URL
    time_range: str  # Optional - time range filter ('day', 'week', 'month', 'year', 'd', 'w', 'm', 'y')
    start_date: str  # Optional - start date filter (YYYY-MM-DD)
    end_date: str  # Optional - end date filter (YYYY-MM-DD)
    country: str  # Optional - country code filter (e.g., 'US', 'GB', 'DE')


class TavilySearchConfig(BaseSearchConfig):
    TAVILY_API_BASE = "https://api.tavily.com"
    
    @staticmethod
    def ui_friendly_name() -> str:
        return "Tavily"
    
    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("TAVILY_API_KEY")
        if not api_key:
            raise ValueError("TAVILY_API_KEY is not set. Set `TAVILY_API_KEY` environment variable.")
        headers["Authorization"] = f"Bearer {api_key}"
        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.
        """
        api_base = api_base or get_secret_str("TAVILY_API_BASE") or self.TAVILY_API_BASE
        
        # Append "/search" to the api base if it's not already there
        if not api_base.endswith("/search"):
            api_base = f"{api_base}/search"

        return api_base
        

    def transform_search_request(
        self,
        query: Union[str, List[str]],
        optional_params: dict,
        **kwargs,
    ) -> Dict:
        """
        Transform Search request to Tavily API format.
        
        Args:
            query: Search query (string or list of strings). Tavily only supports single string queries.
            optional_params: Optional parameters for the request
                - max_results: Maximum number of search results (0-20)
                - search_domain_filter: List of domains to include (max 300) -> maps to `include_domains`
                - exclude_domains: List of domains to exclude (max 150)
                - topic: Category of search ('general', 'news', 'finance')
                - search_depth: Depth of search ('basic', 'advanced')
                - include_answer: Include LLM-generated answer (bool or 'basic', 'advanced')
                - include_raw_content: Include raw HTML content (bool or 'markdown', 'text')
                - include_images: Perform image search (bool)
                - include_image_descriptions: Add descriptions for images (bool)
                - include_favicon: Include favicon URL (bool)
                - time_range: Time range filter ('day', 'week', 'month', 'year', 'd', 'w', 'm', 'y')
                - start_date: Start date filter (YYYY-MM-DD)
                - end_date: End date filter (YYYY-MM-DD)
                - country: Country code filter (e.g., 'US', 'GB', 'DE')
            
        Returns:
            Dict with typed request data following TavilySearchRequest spec
        """
        if isinstance(query, list):
            # Tavily only supports single string queries
            query = " ".join(query)

        request_data: TavilySearchRequest = {
            "query": query,
        }
        
        # Transform Perplexity unified spec parameters to Tavily format
        if "max_results" in optional_params:
            request_data["max_results"] = optional_params["max_results"]
        
        if "search_domain_filter" in optional_params:
            request_data["include_domains"] = optional_params["search_domain_filter"]
        
        if "country" in optional_params:
            # Tavily expects lowercase country names
            request_data["country"] = optional_params["country"].lower()
        
        # Convert to dict before dynamic key assignments
        result_data = dict(request_data)
        
        # pass through all other parameters as-is
        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
        
        return result_data

    def transform_search_response(
        self,
        raw_response: httpx.Response,
        logging_obj: LiteLLMLoggingObj,
        **kwargs,
    ) -> SearchResponse:
        """
        Transform Tavily API response to LiteLLM unified SearchResponse format.
        
        Tavily → LiteLLM mappings:
        - results[].title → SearchResult.title
        - results[].url → SearchResult.url
        - results[].content → SearchResult.snippet
        - No date/last_updated fields in Tavily response (set to None)
        
        Args:
            raw_response: Raw httpx response from Tavily API
            logging_obj: Logging object for tracking
            
        Returns:
            SearchResponse with standardized format
        """
        response_json = raw_response.json()
        
        # Transform results to SearchResult objects
        results = []
        for result in response_json.get("results", []):
            search_result = SearchResult(
                title=result.get("title", ""),
                url=result.get("url", ""),
                snippet=result.get("content", ""),  # Tavily uses "content" instead of "snippet"
                date=None,  # Tavily doesn't provide date in response
                last_updated=None,  # Tavily doesn't provide last_updated in response
            )
            results.append(search_result)
        
        return SearchResponse(
            results=results,
            object="search",
        )

