"""Meilisearch search backend using meilisearch-python-sdk (async)."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any

from search.backends.base import SearchBackend
from search.models import (
    IndexConfig,
    SearchDocument,
    SearchQuery,
    SearchResponse,
    SearchResult,
)

if TYPE_CHECKING:
    from meilisearch_python_sdk import AsyncClient


class MeilisearchBackend(SearchBackend):
    """Meilisearch backend with faceted search, typo tolerance, and filtering.

    Wraps the async ``meilisearch-python-sdk`` client. Each ``IndexConfig``
    maps directly to a Meilisearch index with configurable searchable,
    filterable, and sortable attributes.
    """

    def __init__(self, client: AsyncClient) -> None:
        self._client = client

    async def create_index(self, config: IndexConfig) -> None:
        index = self._client.index(config.name)
        try:
            await self._client.create_index(uid=config.name, primary_key="id")
        except Exception:
            pass  # index may already exist

        if config.searchable_fields:
            await index.update_searchable_attributes(config.searchable_fields)
        if config.filterable_fields:
            await index.update_filterable_attributes(config.filterable_fields)
        if config.sortable_fields:
            await index.update_sortable_attributes(config.sortable_fields)
        if config.ranking_rules:
            await index.update_ranking_rules(config.ranking_rules)

    async def delete_index(self, index_name: str) -> None:
        try:
            await self._client.delete_index_if_exists(index_name)
        except Exception:
            pass

    async def index(self, document: SearchDocument) -> None:
        index = self._client.index(document.index_name)
        doc = {"id": document.id, **document.content}
        await index.add_documents([doc])

    async def bulk_index(self, documents: list[SearchDocument]) -> int:
        if not documents:
            return 0
        by_index: dict[str, list[dict[str, Any]]] = {}
        for doc in documents:
            payload = {"id": doc.id, **doc.content}
            by_index.setdefault(doc.index_name, []).append(payload)

        count = 0
        for index_name, docs in by_index.items():
            index = self._client.index(index_name)
            await index.add_documents(docs)
            count += len(docs)
        return count

    async def delete(self, index_name: str, document_id: str) -> bool:
        index = self._client.index(index_name)
        try:
            await index.delete_document(document_id)
            return True
        except Exception:
            return False

    async def search(self, index_name: str, query: SearchQuery) -> SearchResponse:
        start = time.monotonic()
        index = self._client.index(index_name)

        filter_strs = _build_filter_strings(query.filters)
        sort_list = _build_sort(query.sort_by)
        facets = query.facets or None

        ms_result = await index.search(
            query.query,
            offset=(query.page - 1) * query.per_page,
            limit=query.per_page,
            filter=filter_strs or None,
            sort=sort_list or None,
            facets=facets,
            attributes_to_highlight=["*"] if query.highlight else None,
            highlight_pre_tag="<em>",
            highlight_post_tag="</em>",
        )

        results: list[SearchResult] = []
        for hit in ms_result.hits:
            doc_id = str(hit.get("id", ""))
            formatted = hit.get("_formatted", {})
            highlights = _extract_highlights(formatted, hit)

            document = {k: v for k, v in hit.items() if not k.startswith("_")}
            document.pop("id", None)

            results.append(
                SearchResult(
                    id=doc_id,
                    score=0.0,
                    highlights=highlights,
                    document=document,
                )
            )

        facet_distribution: dict[str, dict[str, int]] = {}
        if hasattr(ms_result, "facet_distribution") and ms_result.facet_distribution:
            facet_distribution = ms_result.facet_distribution

        elapsed = (time.monotonic() - start) * 1000
        total = ms_result.estimated_total_hits if hasattr(ms_result, "estimated_total_hits") else len(results)

        return SearchResponse(
            results=results,
            total=total or 0,
            facets=facet_distribution,
            query_time_ms=round(elapsed, 2),
            page=query.page,
            per_page=query.per_page,
        )

    async def close(self) -> None:
        if hasattr(self._client, "aclose"):
            await self._client.aclose()


# -- helpers -------------------------------------------------------------------


def _build_filter_strings(filters: dict[str, Any]) -> list[str]:
    """Convert a filter dict into Meilisearch filter expressions."""
    parts: list[str] = []
    for key, value in filters.items():
        if isinstance(value, list):
            options = " OR ".join(f'{key} = "{v}"' for v in value)
            parts.append(f"({options})")
        else:
            parts.append(f'{key} = "{value}"')
    return parts


def _build_sort(sort_by: str | None) -> list[str]:
    if not sort_by:
        return []
    if sort_by.startswith("-"):
        return [f"{sort_by[1:]}:desc"]
    return [f"{sort_by}:asc"]


def _extract_highlights(
    formatted: dict[str, Any], original: dict[str, Any]
) -> dict[str, str]:
    """Extract only fields that actually contain highlight markers."""
    highlights: dict[str, str] = {}
    for key, value in formatted.items():
        if key.startswith("_") or key == "id":
            continue
        if isinstance(value, str) and "<em>" in value:
            highlights[key] = value
    return highlights
