"""
Meilisearch Search Service for CuCu Note.

Provides instant keyword search on memos without embeddings.
- Sync memo CRUD → Meilisearch index
- Search by keyword with typo tolerance
- Filter by creator_id for multi-tenant isolation
"""

from __future__ import annotations

import logging
import asyncio
from typing import Any

import meilisearch
from meilisearch.errors import MeilisearchApiError

from config import MEILI_URL, MEILI_MASTER_KEY

logger = logging.getLogger(__name__)

INDEX_NAME = "memos"


class MeiliService:
    """Singleton Meilisearch service for memo search."""

    _instance: MeiliService | None = None
    _client: meilisearch.Client | None = None
    _enabled: bool = False

    def __new__(cls) -> MeiliService:
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    async def initialize(self) -> bool:
        """Connect to Meilisearch and configure the memos index."""
        if not MEILI_URL or not MEILI_MASTER_KEY:
            logger.warning("⚠️ MEILI_URL or MEILI_MASTER_KEY not set — search disabled")
            self._enabled = False
            return False

        try:
            self._client = meilisearch.Client(MEILI_URL, MEILI_MASTER_KEY)

            # Test connection (wrapped in to_thread to avoid blocking event loop)
            await asyncio.to_thread(self._client.health)

            # Create or get index
            try:
                await asyncio.to_thread(self._client.create_index, INDEX_NAME, {"primaryKey": "id"})
                logger.info("✅ Created Meilisearch index: %s", INDEX_NAME)
            except MeilisearchApiError:
                pass  # Index already exists

            # Configure index settings
            index = self._client.index(INDEX_NAME)
            settings = {
                "searchableAttributes": ["content", "tags"],
                "filterableAttributes": ["creator_id", "visibility", "pinned", "row_status"],
                "sortableAttributes": ["created_at_ts"],
                "displayedAttributes": ["id", "uid", "content", "tags", "creator_id",
                                        "visibility", "pinned", "row_status",
                                        "created_at", "updated_at"],
            }
            await asyncio.to_thread(index.update_settings, settings)

            self._enabled = True
            logger.info("✅ Meilisearch connected: %s", MEILI_URL)
            return True

        except Exception as e:
            logger.warning("⚠️ Meilisearch unavailable: %s — search disabled", e)
            self._enabled = False
            return False

    @property
    def enabled(self) -> bool:
        return self._enabled

    def _memo_to_document(self, doc: dict[str, Any]) -> dict[str, Any]:
        """Convert MongoDB memo doc to Meilisearch document."""
        created_at = doc.get("created_at")
        updated_at = doc.get("updated_at")

        # Meilisearch needs a string id and numeric timestamp for sorting
        meili_doc = {
            "id": str(doc.get("id") or doc.get("_id")),
            "uid": doc.get("uid", ""),
            "content": doc.get("content", ""),
            "tags": doc.get("payload", {}).get("tags", []),
            "creator_id": doc.get("creator_id", "anonymous"),
            "visibility": doc.get("visibility", "PRIVATE"),
            "pinned": doc.get("pinned", False),
            "row_status": doc.get("row_status", "NORMAL"),
            "created_at": str(created_at) if created_at else "",
            "updated_at": str(updated_at) if updated_at else "",
            "created_at_ts": int(created_at.timestamp()) if created_at else 0,
        }
        return meili_doc

    def index_memo(self, doc: dict[str, Any]) -> None:
        """Add or update a memo in the search index."""
        if not self._enabled or not self._client:
            return
        try:
            meili_doc = self._memo_to_document(doc)
            self._client.index(INDEX_NAME).add_documents([meili_doc])
            logger.debug("✅ Indexed memo: %s", meili_doc["id"])
        except Exception as e:
            logger.warning("⚠️ Meili index error: %s", e)

    def delete_memo(self, memo_id: str) -> None:
        """Remove a memo from the search index."""
        if not self._enabled or not self._client:
            return
        try:
            self._client.index(INDEX_NAME).delete_document(memo_id)
            logger.debug("🗑️ Deleted memo from index: %s", memo_id)
        except Exception as e:
            logger.warning("⚠️ Meili delete error: %s", e)

    def search(
        self,
        query: str,
        creator_id: str | None = None,
        limit: int = 20,
    ) -> list[dict[str, Any]]:
        """
        Search memos by keyword.

        Returns list of matching memo documents from Meilisearch.
        """
        if not self._enabled or not self._client:
            return []

        try:
            search_params: dict[str, Any] = {
                "limit": limit,
                "sort": ["created_at_ts:desc"],
            }

            # Filter by creator for multi-tenant isolation
            filters = []
            if creator_id:
                filters.append(f'creator_id = "{creator_id}"')
            filters.append('row_status = "NORMAL"')
            search_params["filter"] = " AND ".join(filters)

            result = self._client.index(INDEX_NAME).search(query, search_params)
            return result.get("hits", [])

        except Exception as e:
            logger.warning("⚠️ Meili search error: %s", e)
            return []

    async def sync_all_memos(self) -> int:
        """
        Bulk index all memos from MongoDB into Meilisearch.
        Called on startup to ensure index is up-to-date.
        Returns count of synced documents.
        """
        if not self._enabled or not self._client:
            return 0

        try:
            from common.mongo_client import mongodb_client

            cursor = await mongodb_client.memos.find(
                {"parent": {"$exists": False}},  # Only parent memos, not comments
            )
            docs = await cursor.to_list(length=10000)

            if not docs:
                logger.info("🔐 No memos to sync to Meilisearch")
                return 0

            meili_docs = [self._memo_to_document(doc) for doc in docs]

            # Batch add (wrapped in to_thread)
            index = self._client.index(INDEX_NAME)
            await asyncio.to_thread(index.add_documents, meili_docs)

            logger.info("✅ Synced %d memos to Meilisearch", len(meili_docs))
            return len(meili_docs)

        except Exception as e:
            logger.warning("⚠️ Meili sync error: %s", e)
            return 0


# Singleton
meili_service = MeiliService()
