from typing import Any, Dict, List, Optional

import requests
from langchain_core.language_models import BaseLLM
from loguru import logger

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


class ExaSearchEngine(BaseSearchEngine):
    """Exa.ai search engine implementation with neural search capabilities"""

    # Mark as public search engine
    is_public = True
    # Mark as generic search engine (general web search)
    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,
        llm: Optional[BaseLLM] = None,
        include_full_content: bool = True,
        max_filtered_results: Optional[int] = None,
        search_type: str = "auto",
        include_domains: Optional[List[str]] = None,
        exclude_domains: Optional[List[str]] = None,
        start_published_date: Optional[str] = None,
        end_published_date: Optional[str] = None,
        category: Optional[str] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """
        Initialize the Exa search engine.

        Args:
            max_results: Maximum number of search results
            region: Region code for search results (not used by Exa currently)
            time_period: Time period for search results (not used by Exa currently)
            safe_search: Whether to enable safe search (not used by Exa currently)
            search_language: Language for search results (not used by Exa currently)
            api_key: Exa API key (can also be set in UI 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
            search_type: "auto" (default), "neural", "fast", or "deep"
            include_domains: List of domains to include in search
            exclude_domains: List of domains to exclude from search
            start_published_date: Only links published after this date (YYYY-MM-DD)
            end_published_date: Only links published before this date (YYYY-MM-DD)
            category: Data category to focus on (e.g. 'company', 'news', 'research paper')
            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.search_type = search_type
        self.include_domains = include_domains or []
        self.exclude_domains = exclude_domains or []
        self.start_published_date = start_published_date
        self.end_published_date = end_published_date
        self.category = category

        # Resolve API key using base class method
        self.api_key = self._resolve_api_key(
            api_key,
            "search.engine.web.exa.api_key",
            engine_name="Exa",
            settings_snapshot=settings_snapshot,
        )
        self.base_url = "https://api.exa.ai"

        # Exa handles full content natively via its API (payload["contents"]),
        # so _init_full_search() is intentionally not called here.

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

        Args:
            query: The search query

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

        try:
            # Prepare the request payload
            payload = {
                "query": query[:400],  # Limit query length
                "type": self.search_type,
                "numResults": min(
                    100, self.max_results
                ),  # Exa supports up to 100
            }

            # Add optional parameters if specified
            if self.include_domains:
                payload["includeDomains"] = self.include_domains
            if self.exclude_domains:
                payload["excludeDomains"] = self.exclude_domains
            if self.start_published_date:
                payload["startPublishedDate"] = self.start_published_date
            if self.end_published_date:
                payload["endPublishedDate"] = self.end_published_date
            if self.category:
                payload["category"] = self.category

            # Request text content if full content is enabled
            if self.include_full_content:
                payload["contents"] = {
                    "text": {"maxCharacters": 10000},
                    "highlights": {"maxCharacters": 500, "query": query},
                    "summary": {"query": query},
                }

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

            # Make the API request
            response = safe_post(
                f"{self.base_url}/search",
                json=payload,
                headers={
                    "Content-Type": "application/json",
                    "x-api-key": self.api_key,
                },
                timeout=30,
            )

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

            response.raise_for_status()

            # Parse the response
            data = response.json()
            results = data.get("results", [])

            # Format results as previews
            previews = []
            for i, result in enumerate(results):
                # Extract text content if available
                text_content = result.get("text", "")

                # Use highlights or summary as snippet if available, otherwise use text
                snippet = ""
                highlights = result.get("highlights")
                if highlights and isinstance(highlights, list):
                    # Join highlights with ellipsis
                    snippet = " ... ".join(highlights[:3])
                elif "summary" in result:
                    snippet = result.get("summary", "")
                elif text_content:
                    # Use first 500 chars of text as snippet
                    snippet = text_content[:500]

                # Extract display link
                link = result.get("url", "")
                display_link = self._extract_display_link(link)

                preview = {
                    "id": result.get("id", result.get("url", str(i))),
                    "title": result.get("title", ""),
                    "link": link,
                    "snippet": snippet,
                    "displayed_link": display_link,
                    "position": i,
                }

                # Add optional fields if available
                if "publishedDate" in result:
                    preview["published_date"] = result["publishedDate"]
                if "author" in result:
                    preview["author"] = result["author"]
                if "score" in result:
                    preview["score"] = result["score"]

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

                previews.append(preview)

            logger.info(f"Exa returned {len(previews)} results")
            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 Exa 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 Exa results: {safe_msg}")
            return []
