from typing import Any, Dict, List, Optional

from langchain_core.language_models import BaseLLM
from loguru import logger

from ...config import search_config
from ...security.safe_requests import safe_get
from ..rate_limiting import RateLimitError
from ..search_engine_base import BaseSearchEngine


class MojeekSearchEngine(BaseSearchEngine):
    """
    Mojeek search engine implementation.

    Mojeek is a privacy-focused search engine with its own independent
    web crawler and index. Requires a paid API key from mojeek.com.
    """

    # Mark as public search engine
    is_public = True
    # Mark as generic search engine (general web search)
    is_generic = True
    is_lexical = True
    needs_llm_relevance_filter = True

    def _is_valid_search_result(self, url: str) -> bool:
        """
        Check if a URL is a valid absolute HTTP(S) URL.

        Returns False for relative URLs, empty strings, or non-HTTP schemes.
        """
        if not url or not url.lower().startswith(("http://", "https://")):
            return False
        return True

    def __init__(
        self,
        max_results: int = 10,
        language: str = "en",
        region: str = "",
        safe_search: bool = False,
        api_key: Optional[str] = None,
        llm: Optional[BaseLLM] = None,
        max_filtered_results: Optional[int] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        include_full_content: bool = True,
        **kwargs,
    ):
        """
        Initialize the Mojeek search engine.

        Args:
            max_results: Maximum number of search results
            language: Language code in ISO 639-1 format (e.g. 'en', 'fr')
            region: Country code in ISO 3166-1 alpha-2 format (e.g. 'GB', 'FR')
            safe_search: Whether to enable safe search filtering
            api_key: Mojeek API key
            llm: Language model for relevance filtering
            max_filtered_results: Maximum number of results to keep after filtering
            settings_snapshot: Settings snapshot for thread context
            include_full_content: Whether to include full webpage content
        """
        super().__init__(
            llm=llm,
            max_filtered_results=max_filtered_results,
            max_results=max_results,
            include_full_content=include_full_content,
            settings_snapshot=settings_snapshot,
            **kwargs,
        )

        # Get API key - check params, settings, or env vars
        mojeek_api_key = self._resolve_api_key(
            api_key,
            "search.engine.web.mojeek.api_key",
            engine_name="Mojeek",
            settings_snapshot=settings_snapshot,
        )

        self.search_url = "https://api.mojeek.com/search"
        self.max_results = max_results
        self.language = language
        self.region = region
        self.safe_search = safe_search
        self.api_key = mojeek_api_key

        # If full content is requested, initialize FullSearchResults
        self._init_full_search(
            web_search=self,
            language=language,
            max_results=max_results,
            region=region,
            safe_search=safe_search,
            time_period="y",
        )

    def _get_search_results(self, query: str) -> List[Dict[str, Any]]:
        """
        Get search results from the Mojeek API.

        Args:
            query: The search query

        Returns:
            List of search result dicts
        """
        logger.info(f"Mojeek running search for query: {query}")

        try:
            params = {
                "q": query,
                "api_key": self.api_key,
                "fmt": "json",
                "t": self.max_results,
                "safe": 1 if self.safe_search else 0,
            }

            if self.language:
                params["lb"] = self.language
                params["lbb"] = 100

            if self.region:
                params["rb"] = self.region
                params["rbb"] = 10

            logger.info(f"Sending request to Mojeek API at {self.search_url}")

            response = safe_get(
                self.search_url,
                params=params,
                timeout=15,
            )

            if response.status_code == 403:
                raise RateLimitError(  # noqa: TRY301 — re-raised by except RateLimitError for base class retry
                    "Mojeek API rate limit hit (403 Forbidden)"
                )

            if response.status_code != 200:
                logger.warning(
                    f"Mojeek API returned status {response.status_code}"
                )
                return []

            data = response.json()

            response_data = data.get("response", {})
            if response_data.get("status") != "OK":
                logger.warning(
                    f"Mojeek API response status: "
                    f"{response_data.get('status', 'missing')}"
                )
                return []

            raw_results = response_data.get("results", [])
            results = []
            for result in raw_results:
                url = result.get("url", "")
                if not self._is_valid_search_result(url):
                    continue
                results.append(
                    {
                        "title": result.get("title", ""),
                        "url": url,
                        "content": result.get("desc", ""),
                        "engine": "mojeek",
                        "category": result.get("cats", ""),
                    }
                )

            if results:
                logger.info(f"Mojeek returned {len(results)} valid results")
            else:
                logger.warning(
                    f"Mojeek returned no valid results for query: {query}"
                )

            return results

        except RateLimitError:
            raise
        except Exception:
            logger.exception("Error when searching using Mojeek")
            return []

    def _get_previews(self, query: str) -> List[Dict[str, Any]]:
        """
        Get preview information for Mojeek search results.

        Args:
            query: The search query

        Returns:
            List of preview dictionaries
        """
        logger.info(f"Getting Mojeek previews for query: {query}")

        results = self._get_search_results(query)

        if not results:
            logger.warning(f"No Mojeek results found for query: {query}")
            return []

        previews = []
        for i, result in enumerate(results):
            preview = {
                "id": result.get("url", "") or f"mojeek-result-{i}",
                "title": result.get("title", ""),
                "link": result.get("url", ""),
                "snippet": result.get("content", ""),
                "engine": result.get("engine", "mojeek"),
                "category": result.get("category", ""),
            }
            previews.append(preview)

        return previews

    def _get_full_content(
        self, relevant_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Get full content for the relevant search results.

        Args:
            relevant_items: List of relevant preview dictionaries

        Returns:
            List of result dictionaries with full content
        """
        if (
            hasattr(search_config, "SEARCH_SNIPPETS_ONLY")
            and search_config.SEARCH_SNIPPETS_ONLY
        ):
            logger.info("Snippet-only mode, skipping full content retrieval")
            return relevant_items

        if self.include_full_content and hasattr(self, "full_search"):
            logger.info("Retrieving full webpage content")
            try:
                return self.full_search._get_full_content(relevant_items)
            except Exception:
                logger.exception("Error retrieving full content")

        return relevant_items
