"""
WebSearchTool - Web search capabilities for groknroll

Provides web search functionality using DuckDuckGo's API (no API key required)
with optional support for Brave Search API (requires API key).

Features:
- DuckDuckGo instant answers (default, no API key)
- Brave Search API integration (optional, requires BRAVE_API_KEY)
- Result filtering and formatting
- Safe search options
- Rate limiting
"""

import os
import time
import urllib.parse
from dataclasses import dataclass, field
from typing import Any, Optional

from groknroll.tools.base_tool import BaseTool

# Optional imports for web requests
try:
    import httpx

    HTTPX_AVAILABLE = True
except ImportError:
    HTTPX_AVAILABLE = False

try:
    from duckduckgo_search import DDGS

    DDGS_AVAILABLE = True
except ImportError:
    DDGS_AVAILABLE = False


@dataclass
class SearchResult:
    """A single search result."""

    title: str
    url: str
    snippet: str
    source: str = ""  # Which search engine provided this result

    def to_dict(self) -> dict[str, str]:
        """Convert to dictionary."""
        return {
            "title": self.title,
            "url": self.url,
            "snippet": self.snippet,
            "source": self.source,
        }

    def to_markdown(self) -> str:
        """Format as markdown."""
        return f"**[{self.title}]({self.url})**\n{self.snippet}"


@dataclass
class SearchResponse:
    """Response from a web search."""

    query: str
    results: list[SearchResult] = field(default_factory=list)
    total_results: int = 0
    search_engine: str = ""
    error: Optional[str] = None

    @property
    def success(self) -> bool:
        """Check if search was successful."""
        return self.error is None and len(self.results) > 0

    def to_text(self) -> str:
        """Format results as plain text for LLM consumption."""
        if self.error:
            return f"Search failed: {self.error}"

        if not self.results:
            return f"No results found for: {self.query}"

        lines = [f"Search results for: {self.query}", f"Source: {self.search_engine}", ""]

        for i, result in enumerate(self.results, 1):
            lines.append(f"{i}. {result.title}")
            lines.append(f"   URL: {result.url}")
            lines.append(f"   {result.snippet}")
            lines.append("")

        return "\n".join(lines)

    def to_markdown(self) -> str:
        """Format results as markdown."""
        if self.error:
            return f"**Search Error:** {self.error}"

        if not self.results:
            return f"No results found for: *{self.query}*"

        lines = [f"## Search Results: {self.query}", f"*Source: {self.search_engine}*", ""]

        for i, result in enumerate(self.results, 1):
            lines.append(f"{i}. {result.to_markdown()}")
            lines.append("")

        return "\n".join(lines)


class WebSearchTool(BaseTool):
    """
    Web search tool using DuckDuckGo or Brave Search API.

    Accepts:
        query: str - Search query
        max_results: int - Maximum results to return (default: 5)
        search_type: str - "text", "news", or "instant" (default: "text")
        safe_search: bool - Enable safe search (default: True)

    Returns:
        str - Formatted search results

    Raises:
        ValueError: If query is empty or invalid
        RuntimeError: If no search provider is available

    Example:
        tool = WebSearchTool()
        results = await tool.execute(query="Python async programming")
    """

    # Rate limiting
    _last_search_time: float = 0.0
    _min_interval: float = 1.0  # Minimum seconds between searches

    def __init__(
        self,
        brave_api_key: Optional[str] = None,
        prefer_brave: bool = False,
    ):
        """
        Initialize WebSearchTool.

        Args:
            brave_api_key: Optional Brave Search API key. If not provided,
                          falls back to BRAVE_API_KEY environment variable.
            prefer_brave: If True, prefer Brave Search over DuckDuckGo when available.
        """
        self._brave_api_key = brave_api_key or os.environ.get("BRAVE_API_KEY")
        self._prefer_brave = prefer_brave

    @property
    def name(self) -> str:
        """Tool identifier."""
        return "websearch"

    @property
    def description(self) -> str:
        """Human-readable description."""
        return "Search the web for information using DuckDuckGo or Brave Search"

    def get_parameters_schema(self) -> dict[str, Any]:
        """JSON Schema for tool parameters - Anthropic API compatible."""
        return {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "The search query to execute",
                },
                "max_results": {
                    "type": "integer",
                    "description": "Maximum number of results to return (default: 5, max: 10)",
                    "default": 5,
                    "minimum": 1,
                    "maximum": 10,
                },
                "search_type": {
                    "type": "string",
                    "description": "Type of search: 'text' for web results, 'news' for news articles",
                    "enum": ["text", "news"],
                    "default": "text",
                },
                "safe_search": {
                    "type": "boolean",
                    "description": "Enable safe search filtering (default: True)",
                    "default": True,
                },
            },
            "required": ["query"],
        }

    def validate_params(self, **kwargs) -> dict[str, Any]:
        """Validate search parameters."""
        if "query" not in kwargs:
            raise ValueError("query parameter is required")

        query = kwargs["query"]
        if not query or not query.strip():
            raise ValueError("query cannot be empty")

        # Sanitize and limit query length
        query = query.strip()[:500]
        kwargs["query"] = query

        # Validate max_results
        max_results = kwargs.get("max_results", 5)
        kwargs["max_results"] = max(1, min(10, int(max_results)))

        # Validate search_type
        search_type = kwargs.get("search_type", "text")
        if search_type not in ("text", "news"):
            kwargs["search_type"] = "text"

        return kwargs

    async def execute(self, **kwargs) -> str:
        """
        Execute web search.

        Args:
            **kwargs: Search parameters (query, max_results, search_type, safe_search)

        Returns:
            Formatted search results as text
        """
        validated = self.validate_params(**kwargs)
        query = validated["query"]
        max_results = validated.get("max_results", 5)
        search_type = validated.get("search_type", "text")
        safe_search = validated.get("safe_search", True)

        # Rate limiting
        self._enforce_rate_limit()

        # Try search providers in order of preference
        response: Optional[SearchResponse] = None

        if self._prefer_brave and self._brave_api_key:
            response = await self._search_brave(query, max_results, search_type, safe_search)
            if response and response.error:
                # Fall back to DuckDuckGo on Brave error
                response = None

        if response is None and DDGS_AVAILABLE:
            response = await self._search_duckduckgo(query, max_results, search_type, safe_search)

        if response is None and self._brave_api_key and not self._prefer_brave:
            response = await self._search_brave(query, max_results, search_type, safe_search)

        if response is None:
            # No search provider available
            providers_needed = []
            if not DDGS_AVAILABLE:
                providers_needed.append("pip install duckduckgo-search")
            if not self._brave_api_key:
                providers_needed.append("set BRAVE_API_KEY environment variable")

            return (
                f"No search provider available. To enable search:\n"
                f"  - {' OR '.join(providers_needed)}"
            )

        return response.to_text()

    def _enforce_rate_limit(self) -> None:
        """Enforce minimum interval between searches."""
        now = time.time()
        elapsed = now - self._last_search_time
        if elapsed < self._min_interval:
            time.sleep(self._min_interval - elapsed)
        self._last_search_time = time.time()

    async def _search_duckduckgo(
        self,
        query: str,
        max_results: int,
        search_type: str,
        safe_search: bool,
    ) -> SearchResponse:
        """Search using DuckDuckGo."""
        try:
            with DDGS() as ddgs:
                safesearch = "moderate" if safe_search else "off"

                if search_type == "news":
                    raw_results = list(
                        ddgs.news(query, max_results=max_results, safesearch=safesearch)
                    )
                else:
                    raw_results = list(
                        ddgs.text(query, max_results=max_results, safesearch=safesearch)
                    )

                results = []
                for r in raw_results:
                    results.append(
                        SearchResult(
                            title=r.get("title", ""),
                            url=r.get("href", r.get("url", "")),
                            snippet=r.get("body", r.get("description", "")),
                            source="DuckDuckGo",
                        )
                    )

                return SearchResponse(
                    query=query,
                    results=results,
                    total_results=len(results),
                    search_engine="DuckDuckGo",
                )

        except Exception as e:
            return SearchResponse(
                query=query,
                search_engine="DuckDuckGo",
                error=f"DuckDuckGo search failed: {str(e)}",
            )

    async def _search_brave(
        self,
        query: str,
        max_results: int,
        search_type: str,
        safe_search: bool,
    ) -> SearchResponse:
        """Search using Brave Search API."""
        if not HTTPX_AVAILABLE:
            return SearchResponse(
                query=query,
                search_engine="Brave",
                error="httpx library not available. Install with: pip install httpx",
            )

        if not self._brave_api_key:
            return SearchResponse(
                query=query,
                search_engine="Brave",
                error="Brave API key not configured",
            )

        try:
            endpoint = "https://api.search.brave.com/res/v1/web/search"
            if search_type == "news":
                endpoint = "https://api.search.brave.com/res/v1/news/search"

            params = {
                "q": query,
                "count": max_results,
                "safesearch": "moderate" if safe_search else "off",
            }

            headers = {
                "Accept": "application/json",
                "X-Subscription-Token": self._brave_api_key,
            }

            async with httpx.AsyncClient() as client:
                response = await client.get(endpoint, params=params, headers=headers, timeout=10.0)
                response.raise_for_status()
                data = response.json()

            results = []
            web_results = data.get("web", {}).get("results", [])
            if search_type == "news":
                web_results = data.get("results", [])

            for r in web_results[:max_results]:
                results.append(
                    SearchResult(
                        title=r.get("title", ""),
                        url=r.get("url", ""),
                        snippet=r.get("description", ""),
                        source="Brave",
                    )
                )

            return SearchResponse(
                query=query,
                results=results,
                total_results=len(results),
                search_engine="Brave Search",
            )

        except httpx.HTTPStatusError as e:
            return SearchResponse(
                query=query,
                search_engine="Brave",
                error=f"Brave API error: {e.response.status_code}",
            )
        except Exception as e:
            return SearchResponse(
                query=query,
                search_engine="Brave",
                error=f"Brave search failed: {str(e)}",
            )
