"""Abstract base class for search engine backends."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any


class BaseSearchBackend(ABC):
    """Abstract interface for search engine backends.

    Modelled after Django's email backend pattern. Consuming projects
    swap backends via the ICV_SEARCH_BACKEND setting.

    All methods raise SearchBackendError on failure.
    """

    def __init__(self, url: str, api_key: str, timeout: int = 30, **kwargs: Any) -> None:
        self.url = url
        self.api_key = api_key
        self.timeout = timeout

    @abstractmethod
    def create_index(self, uid: str, primary_key: str = "id") -> dict[str, Any]:
        """Provision a new index in the engine."""

    @abstractmethod
    def delete_index(self, uid: str) -> None:
        """Remove an index and all its documents."""

    @abstractmethod
    def update_settings(self, uid: str, settings: dict[str, Any]) -> dict[str, Any]:
        """Push index settings (searchable, filterable, sortable attrs, etc.)."""

    @abstractmethod
    def get_settings(self, uid: str) -> dict[str, Any]:
        """Retrieve current settings from the engine."""

    @abstractmethod
    def add_documents(self, uid: str, documents: list[dict[str, Any]], primary_key: str = "id") -> dict[str, Any]:
        """Add or update documents. Returns engine task info."""

    @abstractmethod
    def delete_documents(self, uid: str, document_ids: list[str]) -> dict[str, Any]:
        """Remove documents by ID. Returns engine task info."""

    @abstractmethod
    def clear_documents(self, uid: str) -> dict[str, Any]:
        """Remove ALL documents from an index without deleting the index itself."""

    @abstractmethod
    def search(self, uid: str, query: str, **params: Any) -> dict[str, Any]:
        """Execute a search query. Returns engine response."""

    @abstractmethod
    def get_stats(self, uid: str) -> dict[str, Any]:
        """Get index stats (document count, size, etc.)."""

    @abstractmethod
    def health(self) -> bool:
        """Check engine connectivity. Returns True if healthy."""

    def get_task(self, task_uid: str) -> dict[str, Any]:
        """Check status of an async engine task. Optional — not all engines support this."""
        raise NotImplementedError(f"{self.__class__.__name__} does not support task tracking.")

    def multi_search(self, queries: list[dict[str, Any]]) -> list[dict[str, Any]]:
        """Execute multiple search queries, returning one result dict per query.

        Each query dict must contain ``uid`` (the engine index UID) and ``query``
        (the search string). Optional keys: ``filter``, ``sort``, ``limit``,
        ``offset``, ``facets``, ``attributesToHighlight``.

        The default implementation calls :meth:`search` in a loop. Backends
        that natively support multi-search (e.g. Meilisearch) should override
        this method.

        Returns:
            A list of raw engine response dicts in the same order as ``queries``.
        """
        results: list[dict[str, Any]] = []
        for query in queries:
            uid = query["uid"]
            q = query.get("query", "")
            params = {k: v for k, v in query.items() if k not in ("uid", "query")}
            results.append(self.search(uid=uid, query=q, **params))
        return results

    def swap_indexes(self, pairs: list[tuple[str, str]]) -> dict[str, Any]:
        """Atomically swap index names. Not all engines support this.

        Args:
            pairs: List of (index_a, index_b) tuples to swap.

        Returns:
            Engine task info.
        """
        raise NotImplementedError(f"{self.__class__.__name__} does not support index swaps.")
